diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0cbe8ad23..ca3d87b12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,147 +2,163 @@ name: CI on: [push, pull_request, workflow_dispatch] jobs: - build_linux: - name: Ubuntu Build, Check, and Test + build_netbsd: + name: NetBSD Build, Check, and Test + runs-on: ubuntu-latest + env: + PKGSRC_BRANCH: 2024Q1 + steps: + - uses: actions/checkout@v4 + - name: Build, Check, and Test + timeout-minutes: 15 + uses: vmactions/netbsd-vm@v1 + with: + release: "10.0" + envs: PKGSRC_BRANCH + usesh: true + copyback: false + prepare: | + PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r | cut -d_ -f1)_${PKGSRC_BRANCH}/All" /usr/sbin/pkg_add pkgin + pkgin -y in gmake git bash python311 + pkgin -y in libxml2 perl zstd + /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/llvm-17.0.6.tgz + /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/clang-17.0.6.tgz + ln -s /usr/pkg/bin/python3.11 /usr/bin/python3 + run: | + git config --global --add safe.directory $(pwd) + gmake release + ./odin version + ./odin report + gmake -C vendor/stb/src + gmake -C vendor/cgltf/src + 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 + (cd tests/issues; ./run.sh) + build_freebsd: + name: FreeBSD Build, Check, and Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Download LLVM + - uses: actions/checkout@v4 + - name: Build, Check, and Test + timeout-minutes: 15 + uses: vmactions/freebsd-vm@v1 + with: + usesh: true + copyback: false + prepare: | + pkg install -y gmake git bash python3 libxml2 llvm17 + run: | + # `set -e` is needed for test failures to register. https://github.com/vmactions/freebsd-vm/issues/72 + set -e -x + git config --global --add safe.directory $(pwd) + gmake release + ./odin version + ./odin report + gmake -C vendor/stb/src + 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 + (cd tests/issues; ./run.sh) + ci: + strategy: + fail-fast: false + matrix: + # MacOS 13 runs on Intel, 14 runs on ARM + os: [ubuntu-latest, macos-13, macos-14] + runs-on: ${{ matrix.os }} + name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + + - name: Download LLVM (Linux) + if: matrix.os == 'ubuntu-latest' run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh sudo ./llvm.sh 17 echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH - - name: build odin - run: ./build_odin.sh release - - name: Odin version - run: ./odin version - timeout-minutes: 1 - - name: Odin report - run: ./odin report - timeout-minutes: 1 - - name: Odin check - run: ./odin check examples/demo -vet - timeout-minutes: 10 - - name: Odin run - run: ./odin run examples/demo - timeout-minutes: 10 - - name: Odin run -debug - run: ./odin run examples/demo -debug - timeout-minutes: 10 - - name: Odin check examples/all - run: ./odin check examples/all -strict-style - timeout-minutes: 10 - - name: Core library tests - run: | - cd tests/core - make - timeout-minutes: 10 - - name: Vendor library tests - run: | - cd tests/vendor - make - timeout-minutes: 10 - - name: Odin internals tests - run: | - cd tests/internal - make - timeout-minutes: 10 - - name: Odin check examples/all for Linux i386 - run: ./odin check examples/all -vet -strict-style -target:linux_i386 - timeout-minutes: 10 - - name: Odin check examples/all for Linux arm64 - run: ./odin check examples/all -vet -strict-style -target:linux_arm64 - timeout-minutes: 10 - - name: Odin check examples/all for FreeBSD amd64 - run: ./odin check examples/all -vet -strict-style -target:freebsd_amd64 - timeout-minutes: 10 - - name: Odin check examples/all for OpenBSD amd64 - run: ./odin check examples/all -vet -strict-style -target:openbsd_amd64 - timeout-minutes: 10 - build_macOS: - name: MacOS Build, Check, and Test - runs-on: macos-latest - steps: - - uses: actions/checkout@v1 - - name: Download LLVM, and setup PATH + + - name: Download LLVM (MacOS Intel) + if: matrix.os == 'macos-13' run: | brew install llvm@17 echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH - - name: build odin - run: ./build_odin.sh release - - name: Odin version - run: ./odin version - timeout-minutes: 1 - - name: Odin report - run: ./odin report - timeout-minutes: 1 - - name: Odin check - run: ./odin check examples/demo -vet - timeout-minutes: 10 - - name: Odin run - run: ./odin run examples/demo - timeout-minutes: 10 - - name: Odin run -debug - run: ./odin run examples/demo -debug - timeout-minutes: 10 - - name: Odin check examples/all - run: ./odin check examples/all -strict-style - timeout-minutes: 10 - - name: Core library tests + + - name: Download LLVM (MacOS ARM) + if: matrix.os == 'macos-14' run: | - cd tests/core - make - timeout-minutes: 10 - - name: Odin internals tests - run: | - cd tests/internal - make - timeout-minutes: 10 - build_macOS_arm: - name: MacOS ARM Build, Check, and Test - runs-on: macos-14 # This is an arm/m1 runner. - steps: - - uses: actions/checkout@v1 - - name: Download LLVM and setup PATH - run: | - brew install llvm@17 + brew install llvm@17 wasmtime echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH - - name: build odin + + - name: Build Odin run: ./build_odin.sh release - name: Odin version run: ./odin version - timeout-minutes: 1 - name: Odin report run: ./odin report - timeout-minutes: 1 + - 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/demo -vet - timeout-minutes: 10 - name: Odin run run: ./odin run examples/demo - timeout-minutes: 10 - name: Odin run -debug run: ./odin run examples/demo -debug - timeout-minutes: 10 - name: Odin check examples/all run: ./odin check examples/all -strict-style - timeout-minutes: 10 - - name: Core library tests + - name: Normal Core library tests + run: ./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false + - name: Optimized Core library tests + run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -define:ODIN_TEST_FANCY=false + - name: Vendor library tests + run: ./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false + - 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 + - name: GitHub Issue tests run: | - cd tests/core - make - timeout-minutes: 10 - - name: Odin internals tests + cd tests/issues + ./run.sh + + - name: Odin check examples/all for Linux i386 + run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386 + if: matrix.os == 'ubuntu-latest' + - name: Odin check examples/all for Linux arm64 + run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64 + if: matrix.os == 'ubuntu-latest' + - name: Odin check examples/all for FreeBSD amd64 + run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64 + if: matrix.os == 'ubuntu-latest' + - name: Odin check examples/all for OpenBSD amd64 + run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64 + if: matrix.os == 'ubuntu-latest' + + - name: Run demo on WASI WASM32 run: | - cd tests/internal - make - timeout-minutes: 10 + ./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo.wasm + wasmtime ./demo.wasm + if: matrix.os == 'macos-14' + build_windows: name: Windows Build, Check, and Test runs-on: windows-2022 + timeout-minutes: 15 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: build Odin shell: cmd run: | @@ -150,72 +166,67 @@ jobs: ./build.bat 1 - name: Odin version run: ./odin version - timeout-minutes: 1 - name: Odin report run: ./odin report - timeout-minutes: 1 - name: Odin check shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat odin check examples/demo -vet - timeout-minutes: 10 - name: Odin run shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat odin run examples/demo - timeout-minutes: 10 - name: Odin run -debug shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat odin run examples/demo -debug - timeout-minutes: 10 - 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 - timeout-minutes: 10 - name: Core library tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - cd tests\core - call build.bat - timeout-minutes: 10 + odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false + - 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 - name: Vendor library tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - cd tests\vendor - call build.bat - timeout-minutes: 10 + odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false - name: Odin internals tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - cd tests\internal - call build.bat - timeout-minutes: 10 + odin test tests/internal -all-packages -define:ODIN_TEST_FANCY=false - name: Odin documentation tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat cd tests\documentation - rem call build.bat - timeout-minutes: 10 + call build.bat - name: core:math/big tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat cd tests\core\math\big call build.bat - timeout-minutes: 10 - name: Odin check examples/all for Windows 32bits shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat odin check examples/all -strict-style -target:windows_i386 - timeout-minutes: 10 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ff90ab57e..e4bc5d81c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -11,7 +11,7 @@ jobs: if: github.repository == 'odin-lang/Odin' runs-on: windows-2022 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: build Odin shell: cmd run: | @@ -45,13 +45,13 @@ jobs: if: github.repository == 'odin-lang/Odin' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: (Linux) Download LLVM run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh - sudo ./llvm.sh 17 - echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH + sudo ./llvm.sh 18 + echo "/usr/lib/llvm-18/bin" >> $GITHUB_PATH - name: build odin run: make nightly - name: Odin run @@ -77,13 +77,13 @@ jobs: build_macos: name: MacOS Build if: github.repository == 'odin-lang/Odin' - runs-on: macos-latest + runs-on: macos-13 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Download LLVM and setup PATH run: | - brew install llvm@17 dylibbundler - echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH + brew install llvm@18 dylibbundler + echo "/usr/local/opt/llvm@18/bin" >> $GITHUB_PATH - name: build odin # These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to # not link with libunwind bundled with LLVM but link with libunwind on the system. @@ -113,11 +113,11 @@ jobs: if: github.repository == 'odin-lang/Odin' runs-on: macos-14 # ARM machine steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Download LLVM and setup PATH run: | - brew install llvm@17 dylibbundler - echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH + brew install llvm@18 dylibbundler + echo "/opt/homebrew/opt/llvm@18/bin" >> $GITHUB_PATH - name: build odin # These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to # not link with libunwind bundled with LLVM but link with libunwind on the system. @@ -146,16 +146,16 @@ jobs: runs-on: [ubuntu-latest] needs: [build_windows, build_macos, build_macos_arm, build_ubuntu] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - uses: actions/setup-python@v2 with: python-version: '3.8.x' - - name: Install B2 CLI + - name: Install B2 SDK shell: bash run: | python -m pip install --upgrade pip - pip install --upgrade b2 + pip install --upgrade b2sdk - name: Display Python version run: python -c "import sys; print(sys.version)" @@ -188,24 +188,9 @@ jobs: BUCKET: ${{ secrets.B2_BUCKET }} DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }} run: | - echo Authorizing B2 account - b2 authorize-account "$APPID" "$APPKEY" - - echo Uploading artifcates to B2 - chmod +x ./ci/upload_create_nightly.sh - ./ci/upload_create_nightly.sh "$BUCKET" windows-amd64 windows_artifacts/ - ./ci/upload_create_nightly.sh "$BUCKET" ubuntu-amd64 ubuntu_artifacts/dist.zip - ./ci/upload_create_nightly.sh "$BUCKET" macos-amd64 macos_artifacts/dist.zip - ./ci/upload_create_nightly.sh "$BUCKET" macos-arm64 macos_arm_artifacts/dist.zip - - echo Deleting old artifacts in B2 - python3 ci/delete_old_binaries.py "$BUCKET" "$DAYS_TO_KEEP" - - echo Creating nightly.json - python3 ci/create_nightly_json.py "$BUCKET" > nightly.json - - echo Uploading nightly.json - b2 upload-file "$BUCKET" nightly.json nightly.json - - echo Clear B2 account info - b2 clear-account + python3 ci/nightly.py artifact windows-amd64 windows_artifacts/ + python3 ci/nightly.py artifact ubuntu-amd64 ubuntu_artifacts/dist.zip + python3 ci/nightly.py artifact macos-amd64 macos_artifacts/dist.zip + python3 ci/nightly.py artifact macos-arm64 macos_arm_artifacts/dist.zip + python3 ci/nightly.py prune + python3 ci/nightly.py json diff --git a/.gitignore b/.gitignore index 228f006a3..4c3c98b72 100644 --- a/.gitignore +++ b/.gitignore @@ -24,34 +24,6 @@ bld/ ![Cc]ore/[Ll]og/ tests/documentation/verify/ tests/documentation/all.odin-doc -tests/internal/test_map -tests/internal/test_pow -tests/internal/test_rtti -tests/core/test_core_compress -tests/core/test_core_container -tests/core/test_core_filepath -tests/core/test_core_fmt -tests/core/test_core_i18n -tests/core/test_core_image -tests/core/test_core_libc -tests/core/test_core_match -tests/core/test_core_math -tests/core/test_core_net -tests/core/test_core_os_exit -tests/core/test_core_reflect -tests/core/test_core_strings -tests/core/test_crypto -tests/core/test_hash -tests/core/test_hxa -tests/core/test_json -tests/core/test_linalg_glsl_math -tests/core/test_noise -tests/core/test_varint -tests/core/test_xml -tests/core/test_core_slice -tests/core/test_core_thread -tests/core/test_core_runtime -tests/vendor/vendor_botan # Visual Studio 2015 cache/options directory .vs/ # Visual Studio Code options directory @@ -59,6 +31,7 @@ tests/vendor/vendor_botan # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ demo +benchmark # MSTest test Results [Tt]est[Rr]esult*/ @@ -299,7 +272,7 @@ bin/ # - Linux/MacOS odin !odin/ -odin.dSYM +**/*.dSYM *.bin demo.bin libLLVM*.so* @@ -318,4 +291,6 @@ build.sh !core/debug/ # RAD debugger project file -*.raddbg \ No newline at end of file +*.raddbg + +misc/featuregen/featuregen diff --git a/LLVM-C.dll b/LLVM-C.dll index 6393857b4..ee03a2acd 100644 Binary files a/LLVM-C.dll and b/LLVM-C.dll differ diff --git a/base/builtin/builtin.odin b/base/builtin/builtin.odin index 5cba3c8ea..c4a9b141f 100644 --- a/base/builtin/builtin.odin +++ b/base/builtin/builtin.odin @@ -126,3 +126,5 @@ clamp :: proc(value, minimum, maximum: T) -> T --- soa_zip :: proc(slices: ...) -> #soa[]Struct --- soa_unzip :: proc(value: $S/#soa[]$E) -> (slices: ...) --- + +unreachable :: proc() -> ! --- diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index dca33bfd9..37a42b904 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -38,9 +38,12 @@ count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_sim reverse_bits :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) --- -overflow_add :: proc(lhs, rhs: $T) -> (T, bool) --- -overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) --- -overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) --- +overflow_add :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok --- +overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok --- +overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok --- + +add_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) --- +sub_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) --- sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) --- @@ -73,6 +76,8 @@ expect :: proc(val, expected_val: T) -> T --- // Linux and Darwin Only syscall :: proc(id: uintptr, args: ..uintptr) -> uintptr --- +// FreeBSD, NetBSD, et cetera +syscall_bsd :: proc(id: uintptr, args: ..uintptr) -> (uintptr, bool) --- // Atomics @@ -167,17 +172,23 @@ type_is_matrix :: proc($T: typeid) -> bool --- type_has_nil :: proc($T: typeid) -> bool --- +type_is_matrix_row_major :: proc($T: typeid) -> bool where type_is_matrix(T) --- +type_is_matrix_column_major :: proc($T: typeid) -> bool where type_is_matrix(T) --- + type_is_specialization_of :: proc($T, $S: typeid) -> bool --- -type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) --- -type_union_tag_type :: proc($T: typeid) -> typeid where type_is_union(T) --- -type_union_tag_offset :: proc($T: typeid) -> uintptr where type_is_union(T) --- -type_union_base_tag_value :: proc($T: typeid) -> int where type_is_union(U) --- -type_union_variant_count :: proc($T: typeid) -> int where type_is_union(T) --- -type_variant_type_of :: proc($T: typeid, $index: int) -> typeid where type_is_union(T) --- -type_variant_index_of :: proc($U, $V: typeid) -> int where type_is_union(U) --- +type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) --- +type_union_tag_type :: proc($T: typeid) -> typeid where type_is_union(T) --- +type_union_tag_offset :: proc($T: typeid) -> uintptr where type_is_union(T) --- +type_union_base_tag_value :: proc($T: typeid) -> int where type_is_union(U) --- +type_union_variant_count :: proc($T: typeid) -> int where type_is_union(T) --- +type_variant_type_of :: proc($T: typeid, $index: int) -> typeid where type_is_union(T) --- +type_variant_index_of :: proc($U, $V: typeid) -> int where type_is_union(U) --- -type_has_field :: proc($T: typeid, $name: string) -> bool --- +type_bit_set_elem_type :: proc($T: typeid) -> typeid where type_is_bit_set(T) --- +type_bit_set_underlying_type :: proc($T: typeid) -> typeid where type_is_bit_set(T) --- + +type_has_field :: proc($T: typeid, $name: string) -> bool --- type_field_type :: proc($T: typeid, $name: string) -> typeid --- type_proc_parameter_count :: proc($T: typeid) -> int where type_is_proc(T) --- @@ -186,7 +197,8 @@ type_proc_return_count :: proc($T: typeid) -> int where type_is_proc(T) --- type_proc_parameter_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) --- type_proc_return_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) --- -type_struct_field_count :: proc($T: typeid) -> int where type_is_struct(T) --- +type_struct_field_count :: proc($T: typeid) -> int where type_is_struct(T) --- +type_struct_has_implicit_padding :: proc($T: typeid) -> bool where type_is_struct(T) --- type_polymorphic_record_parameter_count :: proc($T: typeid) -> typeid --- type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V --- @@ -282,6 +294,16 @@ simd_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 --- +// Checks if the current target supports the given target features. +// +// Takes a constant comma-seperated string (eg: "sha512,sse4.1"), or a procedure type which has either +// `@(require_target_feature)` or `@(enable_target_feature)` as its input and returns a boolean indicating +// if all listed features are supported. +has_target_feature :: proc($test: $T) -> bool where type_is_string(T) || type_is_proc(T) --- + + +// Returns the value of the procedure where `x` must be a call expression +procedure_of :: proc(x: $T) -> T where type_is_proc(T) --- // WASM targets only wasm_memory_grow :: proc(index, delta: uintptr) -> int --- @@ -293,9 +315,9 @@ wasm_memory_size :: proc(index: uintptr) -> int --- // 0 - indicates that the thread blocked and then was woken up // 1 - the loaded value from `ptr` did not match `expected`, the thread did not block // 2 - the thread blocked, but the timeout -@(enable_target_feature="atomics") +@(require_target_feature="atomics") wasm_memory_atomic_wait32 :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -> u32 --- -@(enable_target_feature="atomics") +@(require_target_feature="atomics") wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) --- // x86 Targets (i386, amd64) diff --git a/base/runtime/core.odin b/base/runtime/core.odin index c62301c34..56aaefaa9 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -66,7 +66,7 @@ Type_Info_Named :: struct { name: string, base: ^Type_Info, pkg: string, - loc: Source_Code_Location, + loc: ^Source_Code_Location, } Type_Info_Integer :: struct {signed: bool, endianness: Platform_Endianness} Type_Info_Rune :: struct {} @@ -112,23 +112,32 @@ Type_Info_Parameters :: struct { // Only used for procedures parameters and resu } Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually -Type_Info_Struct :: struct { - types: []^Type_Info, - names: []string, - offsets: []uintptr, - usings: []bool, - tags: []string, - is_packed: bool, - is_raw_union: bool, - is_no_copy: bool, - custom_align: bool, +Type_Info_Struct_Flags :: distinct bit_set[Type_Info_Struct_Flag; u8] +Type_Info_Struct_Flag :: enum u8 { + packed = 0, + raw_union = 1, + no_copy = 2, + align = 3, +} - equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set +Type_Info_Struct :: struct { + // Slice these with `field_count` + types: [^]^Type_Info `fmt:"v,field_count"`, + names: [^]string `fmt:"v,field_count"`, + offsets: [^]uintptr `fmt:"v,field_count"`, + usings: [^]bool `fmt:"v,field_count"`, + tags: [^]string `fmt:"v,field_count"`, + + field_count: i32, + + flags: Type_Info_Struct_Flags, // These are only set iff this structure is an SOA structure soa_kind: Type_Info_Struct_Soa_Kind, + soa_len: i32, soa_base_type: ^Type_Info, - soa_len: int, + + equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set } Type_Info_Union :: struct { variants: []^Type_Info, @@ -142,9 +151,9 @@ Type_Info_Union :: struct { shared_nil: bool, } Type_Info_Enum :: struct { - base: ^Type_Info, - names: []string, - values: []Type_Info_Enum_Value, + base: ^Type_Info, + names: []string, + values: []Type_Info_Enum_Value, } Type_Info_Map :: struct { key: ^Type_Info, @@ -187,11 +196,12 @@ Type_Info_Soa_Pointer :: struct { } Type_Info_Bit_Field :: struct { backing_type: ^Type_Info, - names: []string, - types: []^Type_Info, - bit_sizes: []uintptr, - bit_offsets: []uintptr, - tags: []string, + names: [^]string `fmt:"v,field_count"`, + types: [^]^Type_Info `fmt:"v,field_count"`, + bit_sizes: [^]uintptr `fmt:"v,field_count"`, + bit_offsets: [^]uintptr `fmt:"v,field_count"`, + tags: [^]string `fmt:"v,field_count"`, + field_count: int, } Type_Info_Flag :: enum u8 { @@ -273,14 +283,14 @@ Typeid_Kind :: enum u8 { } #assert(len(Typeid_Kind) < 32) -// Typeid_Bit_Field :: bit_field #align(align_of(uintptr)) { -// index: 8*size_of(uintptr) - 8, -// kind: 5, // Typeid_Kind -// named: 1, -// special: 1, // signed, cstring, etc -// reserved: 1, -// } -// #assert(size_of(Typeid_Bit_Field) == size_of(uintptr)); +Typeid_Bit_Field :: bit_field uintptr { + index: uintptr | 8*size_of(uintptr) - 8, + kind: Typeid_Kind | 5, // Typeid_Kind + named: bool | 1, + special: bool | 1, // signed, cstring, etc + reserved: bool | 1, +} +#assert(size_of(Typeid_Bit_Field) == size_of(uintptr)) // NOTE(bill): only the ones that are needed (not all types) // This will be set by the compiler @@ -299,6 +309,8 @@ when ODIN_OS == .Windows { Thread_Detach = 3, } dll_forward_reason: DLL_Forward_Reason + + dll_instance: rawptr } // IMPORTANT NOTE(bill): Must be in this order (as the compiler relies upon it) @@ -397,11 +409,34 @@ Logger :: struct { options: Logger_Options, } + +Random_Generator_Mode :: enum { + Read, + Reset, + Query_Info, +} + +Random_Generator_Query_Info_Flag :: enum u32 { + Cryptographic, + Uniform, + External_Entropy, + Resettable, +} +Random_Generator_Query_Info :: distinct bit_set[Random_Generator_Query_Info_Flag; u32] + +Random_Generator_Proc :: #type proc(data: rawptr, mode: Random_Generator_Mode, p: []byte) + +Random_Generator :: struct { + procedure: Random_Generator_Proc, + data: rawptr, +} + Context :: struct { allocator: Allocator, temp_allocator: Allocator, assertion_failure_proc: Assertion_Failure_Proc, logger: Logger, + random_generator: Random_Generator, user_ptr: rawptr, user_index: int, @@ -470,6 +505,15 @@ Raw_Soa_Pointer :: struct { index: int, } +Raw_Complex32 :: struct {real, imag: f16} +Raw_Complex64 :: struct {real, imag: f32} +Raw_Complex128 :: struct {real, imag: f64} +Raw_Quaternion64 :: struct {imag, jmag, kmag: f16, real: f16} +Raw_Quaternion128 :: struct {imag, jmag, kmag: f32, real: f32} +Raw_Quaternion256 :: struct {imag, jmag, kmag: f64, real: f64} +Raw_Quaternion64_Vector_Scalar :: struct {vector: [3]f16, scalar: f16} +Raw_Quaternion128_Vector_Scalar :: struct {vector: [3]f32, scalar: f32} +Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64} /* @@ -481,7 +525,9 @@ Raw_Soa_Pointer :: struct { Linux, Essence, FreeBSD, + Haiku, OpenBSD, + NetBSD, WASI, JS, Freestanding, @@ -508,6 +554,7 @@ Odin_Arch_Type :: type_of(ODIN_ARCH) Odin_Build_Mode_Type :: enum int { Executable, Dynamic, + Static, Object, Assembly, LLVM_IR, @@ -548,6 +595,19 @@ Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET) */ Odin_Sanitizer_Flags :: type_of(ODIN_SANITIZER_FLAGS) +/* + // Defined internally by the compiler + Odin_Optimization_Mode :: enum int { + None = -1, + Minimal = 0, + Size = 1, + Speed = 2, + Aggressive = 3, + } + + ODIN_OPTIMIZATION_MODE // is a constant +*/ +Odin_Optimization_Mode :: type_of(ODIN_OPTIMIZATION_MODE) ///////////////////////////// // Init Startup Procedures // @@ -683,13 +743,16 @@ __init_context :: proc "contextless" (c: ^Context) { c.logger.procedure = default_logger_proc c.logger.data = nil + + c.random_generator.procedure = default_random_generator_proc + c.random_generator.data = nil } default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) -> ! { when ODIN_OS == .Freestanding { // Do nothing } else { - when !ODIN_DISABLE_ASSERT { + when ODIN_OS != .Orca && !ODIN_DISABLE_ASSERT { print_caller_location(loc) print_string(" ") } @@ -698,7 +761,18 @@ default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code print_string(": ") print_string(message) } - print_byte('\n') + + when ODIN_OS == .Orca { + assert_fail( + cstring(raw_data(loc.file_path)), + cstring(raw_data(loc.procedure)), + loc.line, + "", + cstring(raw_data(orca_stderr_buffer[:orca_stderr_buffer_idx])), + ) + } else { + print_byte('\n') + } } trap() } diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 00c30d3fd..38ad95be8 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -65,7 +65,7 @@ copy :: proc{copy_slice, copy_from_string} // with the old value, and reducing the length of the dynamic array by 1. // // Note: This is an O(1) operation. -// Note: If you the elements to remain in their order, use `ordered_remove`. +// Note: If 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 { @@ -79,7 +79,7 @@ unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_loca // `ordered_remove` removed the element at the specified `index` whilst keeping the order of the other elements. // // Note: This is an O(N) operation. -// Note: If you the elements do not have to remain in their order, prefer `unordered_remove`. +// Note: If the 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 { @@ -163,21 +163,43 @@ pop_front_safe :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bo // `clear` will set the length of a passed dynamic array or map to `0` @builtin -clear :: proc{clear_dynamic_array, clear_map} +clear :: proc{ + clear_dynamic_array, + clear_map, + + clear_soa_dynamic_array, +} // `reserve` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). @builtin -reserve :: proc{reserve_dynamic_array, reserve_map} +reserve :: proc{ + reserve_dynamic_array, + reserve_map, + + reserve_soa, +} @builtin -non_zero_reserve :: proc{non_zero_reserve_dynamic_array} +non_zero_reserve :: proc{ + non_zero_reserve_dynamic_array, + + non_zero_reserve_soa, +} // `resize` will try to resize memory of a passed dynamic array to the requested element count (setting the `len`, and possibly `cap`). @builtin -resize :: proc{resize_dynamic_array} +resize :: proc{ + resize_dynamic_array, + + resize_soa, +} @builtin -non_zero_resize :: proc{non_zero_resize_dynamic_array} +non_zero_resize :: proc{ + non_zero_resize_dynamic_array, + + non_zero_resize_soa, +} // Shrinks the capacity of a dynamic array or map down to the current length, or the given capacity. @builtin @@ -268,7 +290,7 @@ new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_locat return } -DEFAULT_RESERVE_CAPACITY :: 16 +DEFAULT_DYNAMIC_ARRAY_CAPACITY :: 8 @(require_results) make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { @@ -295,7 +317,7 @@ make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allo // Note: Prefer using the procedure group `make`. @(builtin, require_results) make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { - return make_dynamic_array_len_cap(T, 0, DEFAULT_RESERVE_CAPACITY, allocator, loc) + return make_dynamic_array_len_cap(T, 0, 0, allocator, loc) } // `make_dynamic_array_len` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. @@ -311,16 +333,23 @@ make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, alloca // Note: Prefer using the procedure group `make`. @(builtin, require_results) make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { - make_dynamic_array_error_loc(loc, len, cap) - array.allocator = allocator // initialize allocator before just in case it fails to allocate any memory - data := mem_alloc_bytes(size_of(E)*cap, align_of(E), allocator, loc) or_return - s := Raw_Dynamic_Array{raw_data(data), len, cap, allocator} - if data == nil && size_of(E) != 0 { - s.len, s.cap = 0, 0 - } - array = transmute(T)s + err = _make_dynamic_array_len_cap((^Raw_Dynamic_Array)(&array), size_of(E), align_of(E), len, cap, allocator, loc) return } + +@(require_results) +_make_dynamic_array_len_cap :: proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (err: Allocator_Error) { + make_dynamic_array_error_loc(loc, len, cap) + array.allocator = allocator // initialize allocator before just in case it fails to allocate any memory + data := mem_alloc_bytes(size_of_elem*cap, align_of_elem, allocator, loc) or_return + use_zero := data == nil && size_of_elem != 0 + array.data = raw_data(data) + array.len = 0 if use_zero else len + array.cap = 0 if use_zero else cap + array.allocator = allocator + return +} + // `make_map` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. // @@ -364,6 +393,11 @@ make :: proc{ make_dynamic_array_len_cap, make_map, make_multi_pointer, + + make_soa_slice, + make_soa_dynamic_array, + make_soa_dynamic_array_len, + make_soa_dynamic_array_len_cap, } @@ -383,7 +417,7 @@ clear_map :: proc "contextless" (m: ^$T/map[$K]$V) { // // Note: Prefer the procedure group `reserve` @builtin -reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int, loc := #caller_location) -> Allocator_Error { +reserve_map :: proc(m: ^$T/map[$K]$V, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc) if m != nil else nil } @@ -413,106 +447,103 @@ delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value: return } -_append_elem :: #force_inline proc(array: ^$T/[dynamic]$E, arg: E, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +_append_elem :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { - return 0, nil + return } - when size_of(E) == 0 { - array := (^Raw_Dynamic_Array)(array) - array.len += 1 - return 1, nil - } else { - if cap(array) < len(array)+1 { - cap := 2 * cap(array) + max(8, 1) - // do not 'or_return' here as it could be a partial success - if should_zero { - err = reserve(array, cap, loc) - } else { - err = non_zero_reserve(array, cap, loc) - } - } - if cap(array)-len(array) > 0 { - a := (^Raw_Dynamic_Array)(array) - when size_of(E) != 0 { - data := ([^]E)(a.data) - assert(data != nil, loc=loc) - data[a.len] = arg - } - a.len += 1 - return 1, err - } - return 0, err + if array.cap < array.len+1 { + // Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY. + cap := 2 * array.cap + DEFAULT_DYNAMIC_ARRAY_CAPACITY + + // do not 'or_return' here as it could be a partial success + err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc) } + if array.cap-array.len > 0 { + data := ([^]byte)(array.data) + assert(data != nil, loc=loc) + data = data[array.len*size_of_elem:] + intrinsics.mem_copy_non_overlapping(data, arg_ptr, size_of_elem) + array.len += 1 + n = 1 + } + return } @builtin append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elem(array, arg, true, loc=loc) + when size_of(E) == 0 { + (^Raw_Dynamic_Array)(array).len += 1 + return 1, nil + } else { + arg := arg + return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, true, loc=loc) + } } @builtin non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elem(array, arg, false, loc=loc) + when size_of(E) == 0 { + (^Raw_Dynamic_Array)(array).len += 1 + return 1, nil + } else { + arg := arg + return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, false, loc=loc) + } } -_append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, loc := #caller_location, args: ..E) -> (n: int, err: Allocator_Error) #optional_allocator_error { +_append_elems :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, should_zero: bool, loc := #caller_location, args: rawptr, arg_len: int) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return 0, nil } - arg_len := len(args) if arg_len <= 0 { return 0, nil } - when size_of(E) == 0 { - array := (^Raw_Dynamic_Array)(array) - array.len += arg_len - return arg_len, nil - } else { - if cap(array) < len(array)+arg_len { - cap := 2 * cap(array) + max(8, arg_len) + if array.cap < array.len+arg_len { + cap := 2 * array.cap + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len) - // do not 'or_return' here as it could be a partial success - if should_zero { - err = reserve(array, cap, loc) - } else { - err = non_zero_reserve(array, cap, loc) - } - } - arg_len = min(cap(array)-len(array), arg_len) - if arg_len > 0 { - a := (^Raw_Dynamic_Array)(array) - when size_of(E) != 0 { - data := ([^]E)(a.data) - assert(data != nil, loc=loc) - intrinsics.mem_copy(&data[a.len], raw_data(args), size_of(E) * arg_len) - } - a.len += arg_len - } - return arg_len, err + // do not 'or_return' here as it could be a partial success + err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc) } + arg_len := arg_len + arg_len = min(array.cap-array.len, arg_len) + if arg_len > 0 { + data := ([^]byte)(array.data) + assert(data != nil, loc=loc) + data = data[array.len*size_of_elem:] + intrinsics.mem_copy(data, args, size_of_elem * arg_len) // must be mem_copy (overlapping) + array.len += arg_len + } + return arg_len, err } @builtin append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elems(array, true, loc, ..args) + when size_of(E) == 0 { + a := (^Raw_Dynamic_Array)(array) + a.len += len(args) + return len(args), nil + } else { + return _append_elems((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), true, loc, raw_data(args), len(args)) + } } @builtin non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elems(array, false, loc, ..args) + when size_of(E) == 0 { + a := (^Raw_Dynamic_Array)(array) + a.len += len(args) + return len(args), nil + } else { + return _append_elems((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), false, loc, raw_data(args), len(args)) + } } // The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type _append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - args := transmute([]E)arg - if should_zero { - return append_elems(array, ..args, loc=loc) - } else { - return non_zero_append_elems(array, ..args, loc=loc) - } + return _append_elems((^Raw_Dynamic_Array)(array), 1, 1, should_zero, loc, raw_data(arg), len(arg)) } @builtin @@ -540,8 +571,23 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_ } // The append built-in procedure appends elements to the end of a dynamic array -@builtin append :: proc{append_elem, append_elems, append_elem_string} -@builtin non_zero_append :: proc{non_zero_append_elem, non_zero_append_elems, non_zero_append_elem_string} +@builtin append :: proc{ + append_elem, + append_elems, + append_elem_string, + + append_soa_elem, + append_soa_elems, +} + +@builtin non_zero_append :: proc{ + non_zero_append_elem, + non_zero_append_elems, + non_zero_append_elem_string, + + non_zero_append_soa_elem, + non_zero_append_soa_elems, +} @builtin @@ -636,7 +682,7 @@ assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle @builtin -assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { new_size := index + len(args) if len(args) == 0 { ok = true @@ -686,11 +732,10 @@ clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) { // `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). // // Note: Prefer the procedure group `reserve`. -_reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { - if array == nil { +_reserve_dynamic_array :: #force_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { + if a == nil { return nil } - a := (^Raw_Dynamic_Array)(array) if capacity <= a.cap { return nil @@ -701,15 +746,15 @@ _reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: i } assert(a.allocator.procedure != nil) - old_size := a.cap * size_of(E) - new_size := capacity * size_of(E) + old_size := a.cap * size_of_elem + new_size := capacity * size_of_elem allocator := a.allocator new_data: []byte if should_zero { - new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return } else { - new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return } if new_data == nil && new_size > 0 { return .Out_Of_Memory @@ -721,27 +766,24 @@ _reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: i } @builtin -reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { - return _reserve_dynamic_array(array, capacity, true, loc) +reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { + return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, true, loc) } @builtin -non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { - return _reserve_dynamic_array(array, capacity, false, loc) +non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { + return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, false, loc) } -// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`). -// -// Note: Prefer the procedure group `resize` -_resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { - if array == nil { + +_resize_dynamic_array :: #force_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { + if a == nil { return nil } - a := (^Raw_Dynamic_Array)(array) if length <= a.cap { if should_zero && a.len < length { - intrinsics.mem_zero(([^]E)(a.data)[a.len:], (length-a.len)*size_of(E)) + intrinsics.mem_zero(([^]byte)(a.data)[a.len*size_of_elem:], (length-a.len)*size_of_elem) } a.len = max(length, 0) return nil @@ -752,15 +794,15 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, } assert(a.allocator.procedure != nil) - old_size := a.cap * size_of(E) - new_size := length * size_of(E) + old_size := a.cap * size_of_elem + new_size := length * size_of_elem allocator := a.allocator new_data : []byte if should_zero { - new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return } else { - new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return } if new_data == nil && new_size > 0 { return .Out_Of_Memory @@ -772,14 +814,17 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, return nil } +// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`). +// +// Note: Prefer the procedure group `resize` @builtin -resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { - return _resize_dynamic_array(array, length, true, loc=loc) +resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { + return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, true, loc=loc) } @builtin -non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { - return _resize_dynamic_array(array, length, false, loc=loc) +non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { + return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, false, loc=loc) } /* @@ -794,10 +839,13 @@ non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc : Note: Prefer the procedure group `shrink` */ shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { - if array == nil { + return _shrink_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), new_cap, loc) +} + +_shrink_dynamic_array :: proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { + if a == nil { return } - a := (^Raw_Dynamic_Array)(array) new_cap := new_cap if new_cap >= 0 else a.len @@ -810,10 +858,10 @@ shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #call } assert(a.allocator.procedure != nil) - old_size := a.cap * size_of(E) - new_size := new_cap * size_of(E) + old_size := a.cap * size_of_elem + new_size := new_cap * size_of_elem - new_data := mem_resize(a.data, old_size, new_size, align_of(E), a.allocator, loc) or_return + new_data := mem_resize(a.data, old_size, new_size, align_of_elem, a.allocator, loc) or_return a.data = raw_data(new_data) a.len = min(new_cap, a.len) diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index 23f879791..7f7f5f086 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -55,7 +55,7 @@ raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Sl if array == nil { return nil } - field_count := uintptr(intrinsics.type_struct_field_count(E)) + field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)) footer = (^Raw_SOA_Footer_Slice)(uintptr(array) + field_count*size_of(rawptr)) return } @@ -64,12 +64,7 @@ raw_soa_footer_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) -> (footer: ^Ra if array == nil { return nil } - field_count: uintptr - when intrinsics.type_is_array(E) { - field_count = len(E) - } else { - field_count = uintptr(intrinsics.type_struct_field_count(E)) - } + field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)) footer = (^Raw_SOA_Footer_Dynamic_Array)(uintptr(array) + field_count*size_of(rawptr)) return } @@ -98,11 +93,11 @@ make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, alloc ti = type_info_base(ti) si := &ti.variant.(Type_Info_Struct) - field_count := uintptr(intrinsics.type_struct_field_count(E)) + field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)) total_size := 0 for i in 0.. (array: T, err: Allocator_Error) #optional_allocator_error { context.allocator = allocator - reserve_soa(&array, DEFAULT_RESERVE_CAPACITY, loc) or_return + reserve_soa(&array, 0, loc) or_return return array, nil } @@ -187,8 +182,28 @@ resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_locat return nil } +@builtin +non_zero_resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { + if array == nil { + return nil + } + non_zero_reserve_soa(array, length, loc) or_return + footer := raw_soa_footer(array) + footer.len = length + return nil +} + @builtin reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { + 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 { + return _reserve_soa(array, capacity, false, loc) +} + +_reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, zero_memory: bool, loc := #caller_location) -> Allocator_Error { if array == nil { return nil } @@ -213,12 +228,7 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo ti = type_info_base(ti) si := &ti.variant.(Type_Info_Struct) - field_count: uintptr - when intrinsics.type_is_array(E) { - field_count = len(E) - } else { - field_count = uintptr(intrinsics.type_struct_field_count(E)) - } + field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)) assert(footer.cap == old_cap) old_size := 0 @@ -226,7 +236,7 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo max_align :: align_of(E) for i in 0.. (n: int, err: Allocator_Error) #optional_allocator_error { +append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_soa_elem(array, true, arg, loc) +} + +@builtin +non_zero_append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_soa_elem(array, false, arg, loc) +} + +_append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return 0, nil } if cap(array) <= len(array) + 1 { - cap := 2 * cap(array) + 8 - err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success + // Same behavior as append_soa_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY. + cap := 2 * cap(array) + DEFAULT_DYNAMIC_ARRAY_CAPACITY + err = _reserve_soa(array, cap, zero_memory, loc) // do not 'or_return' here as it could be a partial success } footer := raw_soa_footer(array) @@ -290,12 +311,7 @@ append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, arg: E, loc := #caller_locat ti := type_info_of(T) ti = type_info_base(ti) si := &ti.variant.(Type_Info_Struct) - field_count: uintptr - when intrinsics.type_is_array(E) { - field_count = len(E) - } else { - field_count = uintptr(intrinsics.type_struct_field_count(E)) - } + field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)) data := (^rawptr)(array)^ @@ -307,7 +323,7 @@ append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, arg: E, loc := #caller_locat max_align :: align_of(E) for i in 0.. (n: int, err: Allocator_Error) #optional_allocator_error { +append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_soa_elems(array, true, args=args, loc=loc) +} + +@builtin +non_zero_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_soa_elems(array, false, args=args, loc=loc) +} + + +_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast args: []E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return } @@ -337,8 +363,8 @@ append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_l } if cap(array) <= len(array)+arg_len { - cap := 2 * cap(array) + max(8, arg_len) - err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success + cap := 2 * cap(array) + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len) + err = _reserve_soa(array, cap, zero_memory, loc) // do not 'or_return' here as it could be a partial success } arg_len = min(cap(array)-len(array), arg_len) @@ -347,7 +373,7 @@ append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_l ti := type_info_of(typeid_of(T)) ti = type_info_base(ti) si := &ti.variant.(Type_Info_Struct) - field_count := uintptr(intrinsics.type_struct_field_count(E)) + field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)) data := (^rawptr)(array)^ @@ -358,7 +384,7 @@ append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_l max_align :: align_of(E) for i in 0.. Allocator_Error { - when intrinsics.type_struct_field_count(E) != 0 { + field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E) + when field_count != 0 { array := array ptr := (^rawptr)(&array)^ free(ptr, allocator, loc) or_return @@ -398,7 +425,8 @@ delete_soa_slice :: proc(array: $T/#soa[]$E, allocator := context.allocator, loc } delete_soa_dynamic_array :: proc(array: $T/#soa[dynamic]$E, loc := #caller_location) -> Allocator_Error { - when intrinsics.type_struct_field_count(E) != 0 { + field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E) + when field_count != 0 { array := array ptr := (^rawptr)(&array)^ footer := raw_soa_footer(&array) @@ -416,7 +444,8 @@ delete_soa :: proc{ clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) { - when intrinsics.type_struct_field_count(E) != 0 { + field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E) + when field_count != 0 { footer := raw_soa_footer(array) footer.len = 0 } @@ -438,12 +467,7 @@ into_dynamic_soa :: proc(array: $T/#soa[]$E) -> #soa[dynamic]E { allocator = nil_allocator(), } - field_count: uintptr - when intrinsics.type_is_array(E) { - field_count = len(E) - } else { - field_count = uintptr(intrinsics.type_struct_field_count(E)) - } + field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)) array := array dynamic_data := ([^]rawptr)(&d)[:field_count] @@ -467,16 +491,11 @@ unordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, index: int, loc := #cal ti = type_info_base(ti) si := &ti.variant.(Type_Info_Struct) - field_count: uintptr - when intrinsics.type_is_array(E) { - field_count = len(E) - } else { - field_count = uintptr(intrinsics.type_struct_field_count(E)) - } + field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)) data := uintptr(array) for i in 0.. Allocator_Error { +map_reserve_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error { @(require_results) ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr { z := intrinsics.count_leading_zeros(x) @@ -641,7 +641,7 @@ map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_ @(require_results) -map_shrink_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { +map_shrink_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { if m.allocator.procedure == nil { m.allocator = context.allocator } @@ -688,7 +688,7 @@ map_shrink_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_I } @(require_results) -map_free_dynamic :: proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_location) -> Allocator_Error { +map_free_dynamic :: #force_no_inline proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_location) -> Allocator_Error { ptr := rawptr(map_data(m)) size := int(map_total_allocation_size(uintptr(map_cap(m)), info)) err := mem_free_with_size(ptr, size, m.allocator, loc) @@ -700,7 +700,7 @@ map_free_dynamic :: proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_loc } @(require_results) -map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) { +map_lookup_dynamic :: #force_no_inline proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) { if map_len(m) == 0 { return 0, false } @@ -723,7 +723,7 @@ map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, } } @(require_results) -map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) { +map_exists_dynamic :: #force_no_inline proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) { if map_len(m) == 0 { return false } @@ -749,7 +749,7 @@ map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, @(require_results) -map_erase_dynamic :: #force_inline proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (old_k, old_v: uintptr, ok: bool) { +map_erase_dynamic :: #force_no_inline proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (old_k, old_v: uintptr, ok: bool) { index := map_lookup_dynamic(m^, info, k) or_return ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) hs[index] |= TOMBSTONE_MASK diff --git a/base/runtime/entry_unix.odin b/base/runtime/entry_unix.odin index e49698e6e..7d7252625 100644 --- a/base/runtime/entry_unix.odin +++ b/base/runtime/entry_unix.odin @@ -1,5 +1,5 @@ //+private -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku //+no-instrumentation package runtime diff --git a/base/runtime/entry_wasm.odin b/base/runtime/entry_wasm.odin index c608942ba..99cd8201d 100644 --- a/base/runtime/entry_wasm.odin +++ b/base/runtime/entry_wasm.odin @@ -6,15 +6,34 @@ package runtime import "base:intrinsics" when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { - @(link_name="_start", linkage="strong", require, export) - _start :: proc "c" () { - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() + when ODIN_OS == .Orca { + @(linkage="strong", require, export) + oc_on_init :: proc "c" () { + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + } + @(linkage="strong", require, export) + oc_on_terminate :: proc "c" () { + context = default_context() + #force_no_inline _cleanup_runtime() + } + } else { + @(link_name="_start", linkage="strong", require, export) + _start :: proc "c" () { + context = default_context() + + when ODIN_OS == .WASI { + _wasi_setup_args() + } + + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + } + @(link_name="_end", linkage="strong", require, export) + _end :: proc "c" () { + context = default_context() + #force_no_inline _cleanup_runtime() + } } - @(link_name="_end", linkage="strong", require, export) - _end :: proc "c" () { - context = default_context() - #force_no_inline _cleanup_runtime() - } -} \ No newline at end of file +} diff --git a/base/runtime/entry_windows.odin b/base/runtime/entry_windows.odin index 7020e9ea8..1e2dcc21a 100644 --- a/base/runtime/entry_windows.odin +++ b/base/runtime/entry_windows.odin @@ -10,8 +10,9 @@ when ODIN_BUILD_MODE == .Dynamic { DllMain :: proc "system" (hinstDLL: rawptr, fdwReason: u32, lpReserved: rawptr) -> b32 { context = default_context() - // Populate Windows DLL-specific global + // Populate Windows DLL-specific globals dll_forward_reason = DLL_Forward_Reason(fdwReason) + dll_instance = hinstDLL switch dll_forward_reason { case .Process_Attach: diff --git a/base/runtime/error_checks.odin b/base/runtime/error_checks.odin index 742e06a71..32a895c3f 100644 --- a/base/runtime/error_checks.odin +++ b/base/runtime/error_checks.odin @@ -4,6 +4,8 @@ package runtime bounds_trap :: proc "contextless" () -> ! { when ODIN_OS == .Windows { windows_trap_array_bounds() + } else when ODIN_OS == .Orca { + abort_ext("", "", 0, "bounds trap") } else { trap() } @@ -13,6 +15,8 @@ bounds_trap :: proc "contextless" () -> ! { type_assertion_trap :: proc "contextless" () -> ! { when ODIN_OS == .Windows { windows_trap_type_assertion() + } else when ODIN_OS == .Orca { + abort_ext("", "", 0, "type assertion trap") } else { trap() } diff --git a/base/runtime/heap_allocator.odin b/base/runtime/heap_allocator.odin index 75f79ab77..cdad8690e 100644 --- a/base/runtime/heap_allocator.odin +++ b/base/runtime/heap_allocator.odin @@ -97,14 +97,14 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, } -heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { +heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { return _heap_alloc(size, zero_memory) } -heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { +heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { return _heap_resize(ptr, new_size) } -heap_free :: proc(ptr: rawptr) { +heap_free :: proc "contextless" (ptr: rawptr) { _heap_free(ptr) } \ No newline at end of file diff --git a/base/runtime/heap_allocator_orca.odin b/base/runtime/heap_allocator_orca.odin new file mode 100644 index 000000000..9e719bcd0 --- /dev/null +++ b/base/runtime/heap_allocator_orca.odin @@ -0,0 +1,29 @@ +//+build orca +//+private +package runtime + +foreign { + @(link_name="malloc") _orca_malloc :: proc "c" (size: int) -> rawptr --- + @(link_name="calloc") _orca_calloc :: proc "c" (num, size: int) -> rawptr --- + @(link_name="free") _orca_free :: proc "c" (ptr: rawptr) --- + @(link_name="realloc") _orca_realloc :: proc "c" (ptr: rawptr, size: int) -> rawptr --- +} + +_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { + if size <= 0 { + return nil + } + if zero_memory { + return _orca_calloc(1, size) + } else { + return _orca_malloc(size) + } +} + +_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { + return _orca_realloc(ptr, new_size) +} + +_heap_free :: proc "contextless" (ptr: rawptr) { + _orca_free(ptr) +} diff --git a/base/runtime/heap_allocator_other.odin b/base/runtime/heap_allocator_other.odin index 45049c7e9..8a7ad1a47 100644 --- a/base/runtime/heap_allocator_other.odin +++ b/base/runtime/heap_allocator_other.odin @@ -2,14 +2,17 @@ //+private package runtime -_heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { +_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { + context = default_context() unimplemented("base:runtime 'heap_alloc' procedure is not supported on this platform") } -_heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { +_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { + context = default_context() unimplemented("base:runtime 'heap_resize' procedure is not supported on this platform") } -_heap_free :: proc(ptr: rawptr) { +_heap_free :: proc "contextless" (ptr: rawptr) { + context = default_context() unimplemented("base:runtime 'heap_free' procedure is not supported on this platform") -} \ No newline at end of file +} diff --git a/base/runtime/heap_allocator_unix.odin b/base/runtime/heap_allocator_unix.odin index 2b6698885..60af9e761 100644 --- a/base/runtime/heap_allocator_unix.odin +++ b/base/runtime/heap_allocator_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku //+private package runtime @@ -16,7 +16,7 @@ foreign libc { @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: int) -> rawptr --- } -_heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { +_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { if size <= 0 { return nil } @@ -27,12 +27,12 @@ _heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { } } -_heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { +_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { // NOTE: _unix_realloc doesn't guarantee new memory will be zeroed on // POSIX platforms. Ensure your caller takes this into account. return _unix_realloc(ptr, new_size) } -_heap_free :: proc(ptr: rawptr) { +_heap_free :: proc "contextless" (ptr: rawptr) { _unix_free(ptr) } diff --git a/base/runtime/heap_allocator_windows.odin b/base/runtime/heap_allocator_windows.odin index 2097c3671..e07df7559 100644 --- a/base/runtime/heap_allocator_windows.odin +++ b/base/runtime/heap_allocator_windows.odin @@ -14,11 +14,11 @@ foreign kernel32 { HeapFree :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr) -> b32 --- } -_heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { +_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { HEAP_ZERO_MEMORY :: 0x00000008 return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) } -_heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { +_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { if new_size == 0 { _heap_free(ptr) return nil @@ -30,7 +30,7 @@ _heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { HEAP_ZERO_MEMORY :: 0x00000008 return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size)) } -_heap_free :: proc(ptr: rawptr) { +_heap_free :: proc "contextless" (ptr: rawptr) { if ptr == nil { return } diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index 6ca61c721..1a97ade09 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -1,3 +1,4 @@ +//+vet !cast package runtime import "base:intrinsics" @@ -29,7 +30,7 @@ is_power_of_two_int :: #force_inline proc "contextless" (x: int) -> bool { return (x & (x-1)) == 0 } -align_forward_int :: #force_inline proc(ptr, align: int) -> int { +align_forward_int :: #force_inline proc "odin" (ptr, align: int) -> int { assert(is_power_of_two_int(align)) p := ptr @@ -40,6 +41,24 @@ align_forward_int :: #force_inline proc(ptr, align: int) -> int { return p } +is_power_of_two_uint :: #force_inline proc "contextless" (x: uint) -> bool { + if x <= 0 { + return false + } + return (x & (x-1)) == 0 +} + +align_forward_uint :: #force_inline proc "odin" (ptr, align: uint) -> uint { + assert(is_power_of_two_uint(align)) + + p := ptr + modulo := p & (align-1) + if modulo != 0 { + p += align - modulo + } + return p +} + is_power_of_two_uintptr :: #force_inline proc "contextless" (x: uintptr) -> bool { if x <= 0 { return false @@ -47,7 +66,7 @@ is_power_of_two_uintptr :: #force_inline proc "contextless" (x: uintptr) -> bool return (x & (x-1)) == 0 } -align_forward_uintptr :: #force_inline proc(ptr, align: uintptr) -> uintptr { +align_forward_uintptr :: #force_inline proc "odin" (ptr, align: uintptr) -> uintptr { assert(is_power_of_two_uintptr(align)) p := ptr @@ -58,6 +77,18 @@ align_forward_uintptr :: #force_inline proc(ptr, align: uintptr) -> uintptr { return p } +is_power_of_two :: proc { + is_power_of_two_int, + is_power_of_two_uint, + is_power_of_two_uintptr, +} + +align_forward :: proc { + align_forward_int, + align_forward_uint, + align_forward_uintptr, +} + mem_zero :: proc "contextless" (data: rawptr, len: int) -> rawptr { if data == nil { return nil @@ -453,7 +484,7 @@ quaternion256_ne :: #force_inline proc "contextless" (a, b: quaternion256) -> bo string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int) { // NOTE(bill): Duplicated here to remove dependency on package unicode/utf8 - @static accept_sizes := [256]u8{ + @(static, rodata) accept_sizes := [256]u8{ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0f 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1f 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2f @@ -474,7 +505,7 @@ string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int } Accept_Range :: struct {lo, hi: u8} - @static accept_ranges := [5]Accept_Range{ + @(static, rodata) accept_ranges := [5]Accept_Range{ {0x80, 0xbf}, {0xa0, 0xbf}, {0x80, 0x9f}, @@ -612,21 +643,24 @@ abs_quaternion256 :: #force_inline proc "contextless" (x: quaternion256) -> f64 quo_complex32 :: proc "contextless" (n, m: complex32) -> complex32 { - e, f: f16 + nr, ni := f32(real(n)), f32(imag(n)) + mr, mi := f32(real(m)), f32(imag(m)) - if abs(real(m)) >= abs(imag(m)) { - ratio := imag(m) / real(m) - denom := real(m) + ratio*imag(m) - e = (real(n) + imag(n)*ratio) / denom - f = (imag(n) - real(n)*ratio) / denom + e, f: f32 + + if abs(mr) >= abs(mi) { + ratio := mi / mr + denom := mr + ratio*mi + e = (nr + ni*ratio) / denom + f = (ni - nr*ratio) / denom } else { - ratio := real(m) / imag(m) - denom := imag(m) + ratio*real(m) - e = (real(n)*ratio + imag(n)) / denom - f = (imag(n)*ratio - real(n)) / denom + ratio := mr / mi + denom := mi + ratio*mr + e = (nr*ratio + ni) / denom + f = (ni*ratio - nr) / denom } - return complex(e, f) + return complex(f16(e), f16(f)) } @@ -667,15 +701,15 @@ quo_complex128 :: proc "contextless" (n, m: complex128) -> complex128 { } mul_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + q0, q1, q2, q3 := f32(real(q)), f32(imag(q)), f32(jmag(q)), f32(kmag(q)) + r0, r1, r2, r3 := f32(real(r)), f32(imag(r)), f32(jmag(r)), f32(kmag(r)) t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3 t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2 t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 - return quaternion(w=t0, x=t1, y=t2, z=t3) + return quaternion(w=f16(t0), x=f16(t1), y=f16(t2), z=f16(t3)) } mul_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { @@ -703,8 +737,8 @@ mul_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 { } quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + q0, q1, q2, q3 := f32(real(q)), f32(imag(q)), f32(jmag(q)), f32(kmag(q)) + r0, r1, r2, r3 := f32(real(r)), f32(imag(r)), f32(jmag(r)), f32(kmag(r)) invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) @@ -713,7 +747,7 @@ quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 { t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 - return quaternion(w=t0, x=t1, y=t2, z=t3) + return quaternion(w=f16(t0), x=f16(t1), y=f16(t2), z=f16(t3)) } quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { @@ -801,6 +835,10 @@ truncsfhf2 :: proc "c" (value: f32) -> __float16 { } } +@(link_name="__aeabi_d2h", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +aeabi_d2h :: proc "c" (value: f64) -> __float16 { + return truncsfhf2(f32(value)) +} @(link_name="__truncdfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) truncdfhf2 :: proc "c" (value: f64) -> __float16 { @@ -978,26 +1016,26 @@ modti3 :: proc "c" (a, b: i128) -> i128 { bn := (b ~ s_b) - s_b r: u128 = --- - _ = udivmod128(transmute(u128)an, transmute(u128)bn, &r) - return (transmute(i128)r ~ s_a) - s_a + _ = udivmod128(u128(an), u128(bn), &r) + return (i128(r) ~ s_a) - s_a } @(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 { - u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem) - return transmute(i128)u + u := udivmod128(u128(a), u128(b), (^u128)(rem)) + return i128(u) } @(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) divti3 :: proc "c" (a, b: i128) -> i128 { - u := udivmodti4(transmute(u128)a, transmute(u128)b, nil) - return transmute(i128)u + u := udivmodti4(u128(a), u128(b), nil) + return i128(u) } @(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -fixdfti :: proc(a: u64) -> i128 { +fixdfti :: proc "c" (a: u64) -> i128 { significandBits :: 52 typeWidth :: (size_of(u64)*8) exponentBits :: (typeWidth - significandBits - 1) @@ -1042,19 +1080,17 @@ fixdfti :: proc(a: u64) -> i128 { __write_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) { for i in 0..>3]) & (1<<(i&7)) != 0) + dst[j>>3] &~= 1<<(j&7) + dst[j>>3] |= the_bit<<(j&7) } } __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) { for j in 0..>3]) & (1<<(i&7)) != 0) + dst[j>>3] &~= 1<<(j&7) + dst[j>>3] |= the_bit<<(j&7) } -} \ No newline at end of file +} diff --git a/base/runtime/os_specific_bsd.odin b/base/runtime/os_specific_bsd.odin index 9cd065ff6..46ce51166 100644 --- a/base/runtime/os_specific_bsd.odin +++ b/base/runtime/os_specific_bsd.odin @@ -1,4 +1,4 @@ -//+build freebsd, openbsd +//+build freebsd, openbsd, netbsd //+private package runtime @@ -9,7 +9,11 @@ foreign libc { @(link_name="write") _unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int --- - __error :: proc() -> ^i32 --- + when ODIN_OS == .NetBSD { + @(link_name="__errno") __error :: proc() -> ^i32 --- + } else { + __error :: proc() -> ^i32 --- + } } _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { diff --git a/base/runtime/os_specific_darwin.odin b/base/runtime/os_specific_darwin.odin index 61c17a597..1bf29e785 100644 --- a/base/runtime/os_specific_darwin.odin +++ b/base/runtime/os_specific_darwin.odin @@ -5,11 +5,24 @@ package runtime import "base:intrinsics" _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - WRITE :: 0x2000004 STDERR :: 2 - ret := intrinsics.syscall(WRITE, STDERR, uintptr(raw_data(data)), uintptr(len(data))) - if ret < 0 { - return 0, _OS_Errno(-ret) + when ODIN_NO_CRT { + WRITE :: 0x2000004 + ret := intrinsics.syscall(WRITE, STDERR, uintptr(raw_data(data)), uintptr(len(data))) + if ret < 0 { + return 0, _OS_Errno(-ret) + } + return int(ret), 0 + } else { + foreign { + write :: proc(handle: i32, buffer: [^]byte, count: uint) -> int --- + __error :: proc() -> ^i32 --- + } + + if ret := write(STDERR, raw_data(data), len(data)); ret >= 0 { + return int(ret), 0 + } + + return 0, _OS_Errno(__error()^) } - return int(ret), 0 } diff --git a/base/runtime/os_specific_orca.odin b/base/runtime/os_specific_orca.odin new file mode 100644 index 000000000..b6f5930ab --- /dev/null +++ b/base/runtime/os_specific_orca.odin @@ -0,0 +1,43 @@ +//+build orca +//+private +package runtime + +import "base:intrinsics" + +// 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_ext :: proc(file: cstring, function: cstring, line: i32, fmt: cstring, #c_vararg args: ..any) -> ! --- + assert_fail :: proc(file: cstring, function: cstring, line: i32, src: cstring, fmt: cstring, #c_vararg args: ..any) -> ! --- + log_ext :: proc(level: log_level, function: cstring, file: cstring, line: i32, fmt: cstring, #c_vararg args: ..any) --- +} + +// NOTE: This is all pretty gross, don't look. + +// WASM is single threaded so this should be fine. +orca_stderr_buffer: [4096]byte +orca_stderr_buffer_idx: int + +_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + for b in data { + orca_stderr_buffer[orca_stderr_buffer_idx] = b + orca_stderr_buffer_idx += 1 + + if b == '\n' || orca_stderr_buffer_idx == len(orca_stderr_buffer)-1 { + log_ext(.ERROR, "", "", 0, cstring(raw_data(orca_stderr_buffer[:orca_stderr_buffer_idx]))) + orca_stderr_buffer_idx = 0 + } + } + + return len(data), 0 +} diff --git a/base/runtime/os_specific_wasi.odin b/base/runtime/os_specific_wasi.odin index 94fa5fa89..b85d7adea 100644 --- a/base/runtime/os_specific_wasi.odin +++ b/base/runtime/os_specific_wasi.odin @@ -2,10 +2,54 @@ //+private package runtime -import "core:sys/wasm/wasi" +foreign import wasi "wasi_snapshot_preview1" + +@(default_calling_convention="contextless") +foreign wasi { + fd_write :: proc( + fd: i32, + iovs: [][]byte, + n: ^uint, + ) -> u16 --- + + @(private="file") + args_sizes_get :: proc( + num_of_args: ^uint, + size_of_args: ^uint, + ) -> u16 --- + + @(private="file") + args_get :: proc( + argv: [^]cstring, + argv_buf: [^]byte, + ) -> u16 --- +} _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - data := (wasi.ciovec_t)(data) - n, err := wasi.fd_write(1, {data}) + n: uint + err := fd_write(1, {data}, &n) return int(n), _OS_Errno(err) } + +_wasi_setup_args :: proc() { + num_of_args, size_of_args: uint + if errno := args_sizes_get(&num_of_args, &size_of_args); errno != 0 { + return + } + + err: Allocator_Error + if args__, err = make([]cstring, num_of_args); err != nil { + return + } + + args_buf: []byte + if args_buf, err = make([]byte, size_of_args); err != nil { + delete(args__) + return + } + + if errno := args_get(raw_data(args__), raw_data(args_buf)); errno != 0 { + delete(args__) + delete(args_buf) + } +} diff --git a/base/runtime/print.odin b/base/runtime/print.odin index ed5893e15..45f6f01ef 100644 --- a/base/runtime/print.odin +++ b/base/runtime/print.odin @@ -262,7 +262,7 @@ print_typeid :: #force_no_inline proc "contextless" (id: typeid) { } } -@(optimization_mode="size") +@(optimization_mode="favor_size") print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) { if ti == nil { print_string("nil") @@ -401,15 +401,16 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) { } print_string("struct ") - if info.is_packed { print_string("#packed ") } - if info.is_raw_union { print_string("#raw_union ") } - if info.custom_align { + if .packed in info.flags { print_string("#packed ") } + if .raw_union in info.flags { print_string("#raw_union ") } + if .no_copy in info.flags { print_string("#no_copy ") } + if .align in info.flags { print_string("#align(") print_u64(u64(ti.align)) print_string(") ") } print_byte('{') - for name, i in info.names { + for name, i in info.names[:info.field_count] { if i > 0 { print_string(", ") } print_string(name) print_string(": ") @@ -469,7 +470,7 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) { print_string("bit_field ") print_type(info.backing_type) print_string(" {") - for name, i in info.names { + for name, i in info.names[:info.field_count] { if i > 0 { print_string(", ") } print_string(name) print_string(": ") diff --git a/base/runtime/procs.odin b/base/runtime/procs.odin index 454574c35..002a6501f 100644 --- a/base/runtime/procs.odin +++ b/base/runtime/procs.odin @@ -25,13 +25,19 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { RtlMoveMemory(dst, src, len) return dst } -} else when ODIN_NO_CRT || (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) { +} else when ODIN_NO_CRT || (ODIN_OS != .Orca && (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32)) { + // NOTE: on wasm, calls to these procs are generated (by LLVM) with type `i32` instead of `int`. + // + // NOTE: `#any_int` is also needed, because calls that we generate (and package code) + // will be using `int` and need to be converted. + int_t :: i32 when ODIN_ARCH == .wasm64p32 else int + @(link_name="memset", linkage="strong", require) - memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { + memset :: proc "c" (ptr: rawptr, val: i32, #any_int len: int_t) -> rawptr { if ptr != nil && len != 0 { b := byte(val) p := ([^]byte)(ptr) - for i := 0; i < len; i += 1 { + for i := int_t(0); i < len; i += 1 { p[i] = b } } @@ -39,10 +45,10 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { } @(link_name="bzero", linkage="strong", require) - bzero :: proc "c" (ptr: rawptr, len: int) -> rawptr { + bzero :: proc "c" (ptr: rawptr, #any_int len: int_t) -> rawptr { if ptr != nil && len != 0 { p := ([^]byte)(ptr) - for i := 0; i < len; i += 1 { + for i := int_t(0); i < len; i += 1 { p[i] = 0 } } @@ -50,7 +56,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { } @(link_name="memmove", linkage="strong", require) - memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr { + memmove :: proc "c" (dst, src: rawptr, #any_int len: int_t) -> rawptr { d, s := ([^]byte)(dst), ([^]byte)(src) if d == s || len == 0 { return dst @@ -63,7 +69,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { } if s > d && uintptr(s)-uintptr(d) < uintptr(len) { - for i := 0; i < len; i += 1 { + for i := int_t(0); i < len; i += 1 { d[i] = s[i] } return dst @@ -71,10 +77,10 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { return memcpy(dst, src, len) } @(link_name="memcpy", linkage="strong", require) - memcpy :: proc "c" (dst, src: rawptr, len: int) -> rawptr { + memcpy :: proc "c" (dst, src: rawptr, #any_int len: int_t) -> rawptr { d, s := ([^]byte)(dst), ([^]byte)(src) if d != s { - for i := 0; i < len; i += 1 { + for i := int_t(0); i < len; i += 1 { d[i] = s[i] } } @@ -92,4 +98,4 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { } return ptr } -} \ No newline at end of file +} diff --git a/base/runtime/random_generator.odin b/base/runtime/random_generator.odin new file mode 100644 index 000000000..0c4c92bd2 --- /dev/null +++ b/base/runtime/random_generator.odin @@ -0,0 +1,127 @@ +package runtime + +import "base:intrinsics" + +@(require_results) +random_generator_read_bytes :: proc(rg: Random_Generator, p: []byte) -> bool { + if rg.procedure != nil { + rg.procedure(rg.data, .Read, p) + return true + } + return false +} + +@(require_results) +random_generator_read_ptr :: proc(rg: Random_Generator, p: rawptr, len: uint) -> bool { + if rg.procedure != nil { + rg.procedure(rg.data, .Read, ([^]byte)(p)[:len]) + return true + } + return false +} + +@(require_results) +random_generator_query_info :: proc(rg: Random_Generator) -> (info: Random_Generator_Query_Info) { + if rg.procedure != nil { + rg.procedure(rg.data, .Query_Info, ([^]byte)(&info)[:size_of(info)]) + } + return +} + + +random_generator_reset_bytes :: proc(rg: Random_Generator, p: []byte) { + if rg.procedure != nil { + rg.procedure(rg.data, .Reset, p) + } +} + +random_generator_reset_u64 :: proc(rg: Random_Generator, p: u64) { + if rg.procedure != nil { + p := p + rg.procedure(rg.data, .Reset, ([^]byte)(&p)[:size_of(p)]) + } +} + + +Default_Random_State :: struct { + state: u64, + inc: u64, +} + +default_random_generator_proc :: proc(data: rawptr, mode: Random_Generator_Mode, p: []byte) { + @(require_results) + read_u64 :: proc "contextless" (r: ^Default_Random_State) -> u64 { + old_state := r.state + r.state = old_state * 6364136223846793005 + (r.inc|1) + xor_shifted := (((old_state >> 59) + 5) ~ old_state) * 12605985483714917081 + rot := (old_state >> 59) + return (xor_shifted >> rot) | (xor_shifted << ((-rot) & 63)) + } + + @(thread_local) + global_rand_seed: Default_Random_State + + init :: proc "contextless" (r: ^Default_Random_State, seed: u64) { + seed := seed + if seed == 0 { + seed = u64(intrinsics.read_cycle_counter()) + } + r.state = 0 + r.inc = (seed << 1) | 1 + _ = read_u64(r) + r.state += seed + _ = read_u64(r) + } + + r: ^Default_Random_State = --- + if data == nil { + r = &global_rand_seed + } else { + r = cast(^Default_Random_State)data + } + + switch mode { + case .Read: + if r.state == 0 && r.inc == 0 { + init(r, 0) + } + + switch len(p) { + case size_of(u64): + // Fast path for a 64-bit destination. + intrinsics.unaligned_store((^u64)(raw_data(p)), read_u64(r)) + case: + // All other cases. + pos := i8(0) + val := u64(0) + for &v in p { + if pos == 0 { + val = read_u64(r) + pos = 7 + } + v = byte(val) + val >>= 8 + pos -= 1 + } + } + + case .Reset: + seed: u64 + mem_copy_non_overlapping(&seed, raw_data(p), min(size_of(seed), len(p))) + init(r, seed) + + case .Query_Info: + if len(p) != size_of(Random_Generator_Query_Info) { + return + } + info := (^Random_Generator_Query_Info)(raw_data(p)) + info^ += {.Uniform, .Resettable} + } +} + +default_random_generator :: proc "contextless" (state: ^Default_Random_State = nil) -> Random_Generator { + return { + procedure = default_random_generator_proc, + data = state, + } +} \ No newline at end of file diff --git a/base/runtime/udivmod128.odin b/base/runtime/udivmod128.odin index eceb815bf..8cc70df55 100644 --- a/base/runtime/udivmod128.odin +++ b/base/runtime/udivmod128.odin @@ -58,7 +58,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { return u128(n[high] >> _ctz(d[high])) } - sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high]))) + sr = u32(i32(_clz(d[high])) - i32(_clz(n[high]))) if sr > U64_BITS - 2 { if rem != nil { rem^ = a @@ -107,7 +107,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { r[low] = n[high] >> (sr - U64_BITS) } } else { - sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high]))) + sr = u32(i32(_clz(d[high])) - i32(_clz(n[high]))) if sr > U64_BITS - 1 { if rem != nil { @@ -143,7 +143,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { r_all = transmute(u128)r s := i128(b - r_all - 1) >> (U128_BITS - 1) carry = u32(s & 1) - r_all -= b & transmute(u128)s + r_all -= b & u128(s) r = transmute([2]u64)r_all } diff --git a/base/runtime/wasm_allocator.odin b/base/runtime/wasm_allocator.odin new file mode 100644 index 000000000..6bafaa489 --- /dev/null +++ b/base/runtime/wasm_allocator.odin @@ -0,0 +1,871 @@ +//+build wasm32, wasm64p32 +package runtime + +import "base:intrinsics" + +/* +Port of emmalloc, modified for use in Odin. + +Invariants: + - Per-allocation header overhead is 8 bytes, smallest allocated payload + amount is 8 bytes, and a multiple of 4 bytes. + - Acquired memory blocks are subdivided into disjoint regions that lie + next to each other. + - A region is either in used or free. + Used regions may be adjacent, and a used and unused region + may be adjacent, but not two unused ones - they would be + merged. + - Memory allocation takes constant time, unless the alloc needs to wasm_memory_grow() + or memory is very close to being exhausted. + - Free and used regions are managed inside "root regions", which are slabs + of memory acquired via wasm_memory_grow(). + - Memory retrieved using wasm_memory_grow() can not be given back to the OS. + Therefore, frees are internal to the allocator. + +Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file. + +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. +*/ + +WASM_Allocator :: struct #no_copy { + // The minimum alignment of allocations. + alignment: uint, + // A region that contains as payload a single forward linked list of pointers to + // root regions of each disjoint region blocks. + list_of_all_regions: ^Root_Region, + // For each of the buckets, maintain a linked list head node. The head node for each + // free region is a sentinel node that does not actually represent any free space, but + // the sentinel is used to avoid awkward testing against (if node == freeRegionHeadNode) + // when adding and removing elements from the linked list, i.e. we are guaranteed that + // the sentinel node is always fixed and there, and the actual free region list elements + // start at free_region_buckets[i].next each. + free_region_buckets: [NUM_FREE_BUCKETS]Region, + // A bitmask that tracks the population status for each of the 64 distinct memory regions: + // a zero at bit position i means that the free list bucket i is empty. This bitmask is + // used to avoid redundant scanning of the 64 different free region buckets: instead by + // looking at the bitmask we can find in constant time an index to a free region bucket + // that contains free memory of desired size. + free_region_buckets_used: BUCKET_BITMASK_T, + // Because wasm memory can only be allocated in pages of 64k at a time, we keep any + // spilled/unused bytes that are left from the allocated pages here, first using this + // when bytes are needed. + spill: []byte, + // Mutex for thread safety, only used if the target feature "atomics" is enabled. + mu: Mutex_State, +} + +// Not required to be called, called on first allocation otherwise. +wasm_allocator_init :: proc(a: ^WASM_Allocator, alignment: uint = 8) { + assert(is_power_of_two(alignment), "alignment must be a power of two") + assert(alignment > 4, "alignment must be more than 4") + + a.alignment = alignment + + for i in 0.. Allocator { + return wasm_allocator(&global_default_wasm_allocator_data) +} + +wasm_allocator :: proc(a: ^WASM_Allocator) -> Allocator { + return { + data = a, + procedure = wasm_allocator_proc, + } +} + +wasm_allocator_proc :: proc(a: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + a := (^WASM_Allocator)(a) + if a == nil { + a = &global_default_wasm_allocator_data + } + + if a.alignment == 0 { + wasm_allocator_init(a) + } + + switch mode { + case .Alloc: + ptr := aligned_alloc(a, uint(alignment), uint(size), loc) + if ptr == nil { + return nil, .Out_Of_Memory + } + intrinsics.mem_zero(ptr, size) + return ([^]byte)(ptr)[:size], nil + + case .Alloc_Non_Zeroed: + ptr := aligned_alloc(a, uint(alignment), uint(size), loc) + if ptr == nil { + return nil, .Out_Of_Memory + } + return ([^]byte)(ptr)[:size], nil + + case .Resize: + ptr := aligned_realloc(a, old_memory, uint(alignment), uint(size), loc) + if ptr == nil { + return nil, .Out_Of_Memory + } + + bytes := ([^]byte)(ptr)[:size] + + if size > old_size { + new_region := raw_data(bytes[old_size:]) + intrinsics.mem_zero(new_region, size - old_size) + } + + return bytes, nil + + case .Resize_Non_Zeroed: + ptr := aligned_realloc(a, old_memory, uint(alignment), uint(size), loc) + if ptr == nil { + return nil, .Out_Of_Memory + } + return ([^]byte)(ptr)[:size], nil + + case .Free: + free(a, old_memory, loc) + return nil, nil + + case .Free_All, .Query_Info: + return nil, .Mode_Not_Implemented + + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Resize_Non_Zeroed, .Query_Features } + } + return nil, nil + } + + unreachable() +} + +// Returns the allocated size of the allocator (both free and used). +// If `nil` is given, the global allocator is used. +wasm_allocator_size :: proc(a: ^WASM_Allocator = nil) -> (size: uint) { + a := a + if a == nil { + a = &global_default_wasm_allocator_data + } + + lock(a) + defer unlock(a) + + root := a.list_of_all_regions + for root != nil { + size += uint(uintptr(root.end_ptr) - uintptr(root)) + root = root.next + } + + size += len(a.spill) + + return +} + +// Returns the amount of free memory on the allocator. +// If `nil` is given, the global allocator is used. +wasm_allocator_free_space :: proc(a: ^WASM_Allocator = nil) -> (free: uint) { + a := a + if a == nil { + a = &global_default_wasm_allocator_data + } + + lock(a) + defer unlock(a) + + bucket_index: u64 = 0 + bucket_mask := a.free_region_buckets_used + + for bucket_mask != 0 { + index_add := intrinsics.count_trailing_zeros(bucket_mask) + bucket_index += index_add + bucket_mask >>= index_add + for free_region := a.free_region_buckets[bucket_index].next; free_region != &a.free_region_buckets[bucket_index]; free_region = free_region.next { + free += free_region.size - REGION_HEADER_SIZE + } + bucket_index += 1 + bucket_mask >>= 1 + } + + free += len(a.spill) + + return +} + +@(private="file") +NUM_FREE_BUCKETS :: 64 +@(private="file") +BUCKET_BITMASK_T :: u64 + +// Dynamic memory is subdivided into regions, in the format + +// ..... | ..... | ..... | ..... + +// That is, at the bottom and top end of each memory region, the size of that region is stored. That allows traversing the +// memory regions backwards and forwards. Because each allocation must be at least a multiple of 4 bytes, the lowest two bits of +// each size field is unused. Free regions are distinguished by used regions by having the FREE_REGION_FLAG bit present +// in the size field. I.e. for free regions, the size field is odd, and for used regions, the size field reads even. +@(private="file") +FREE_REGION_FLAG :: 0x1 + +// Attempts to alloc more than this many bytes would cause an overflow when calculating the size of a region, +// therefore allocations larger than this are short-circuited immediately on entry. +@(private="file") +MAX_ALLOC_SIZE :: 0xFFFFFFC7 + +// A free region has the following structure: +// ... + +@(private="file") +Region :: struct { + size: uint, + prev, next: ^Region, + _at_the_end_of_this_struct_size: uint, +} + +// Each memory block starts with a Root_Region at the beginning. +// The Root_Region specifies the size of the region block, and forms a linked +// list of all Root_Regions in the program, starting with `list_of_all_regions` +// below. +@(private="file") +Root_Region :: struct { + size: u32, + next: ^Root_Region, + end_ptr: ^byte, +} + +@(private="file") +Mutex_State :: enum u32 { + Unlocked = 0, + Locked = 1, + Waiting = 2, +} + +@(private="file") +lock :: proc(a: ^WASM_Allocator) { + when intrinsics.has_target_feature("atomics") { + @(cold) + lock_slow :: proc(a: ^WASM_Allocator, curr_state: Mutex_State) { + new_state := curr_state // Make a copy of it + + spin_lock: for spin in 0.. 0; i -= 1 { + intrinsics.cpu_relax() + } + } + + // Set just in case 100 iterations did not do it + new_state = .Waiting + + for { + if intrinsics.atomic_exchange_explicit(&a.mu, .Waiting, .Acquire) == .Unlocked { + return + } + + ret := intrinsics.wasm_memory_atomic_wait32((^u32)(&a.mu), u32(new_state), -1) + assert(ret != 0) + intrinsics.cpu_relax() + } + } + + + if v := intrinsics.atomic_exchange_explicit(&a.mu, .Locked, .Acquire); v != .Unlocked { + lock_slow(a, v) + } + } +} + +@(private="file") +unlock :: proc(a: ^WASM_Allocator) { + when intrinsics.has_target_feature("atomics") { + @(cold) + unlock_slow :: proc(a: ^WASM_Allocator) { + for { + s := intrinsics.wasm_memory_atomic_notify32((^u32)(&a.mu), 1) + if s >= 1 { + return + } + } + } + + switch intrinsics.atomic_exchange_explicit(&a.mu, .Unlocked, .Release) { + case .Unlocked: + unreachable() + case .Locked: + // Okay + case .Waiting: + unlock_slow(a) + } + } +} + +@(private="file") +assert_locked :: proc(a: ^WASM_Allocator) { + when intrinsics.has_target_feature("atomics") { + assert(intrinsics.atomic_load(&a.mu) != .Unlocked) + } +} + +@(private="file") +has_alignment_uintptr :: proc(ptr: uintptr, #any_int alignment: uintptr) -> bool { + return ptr & (alignment-1) == 0 +} + +@(private="file") +has_alignment_uint :: proc(ptr: uint, alignment: uint) -> bool { + return ptr & (alignment-1) == 0 +} + +@(private="file") +has_alignment :: proc { + has_alignment_uintptr, + has_alignment_uint, +} + +@(private="file") +REGION_HEADER_SIZE :: 2*size_of(uint) + +@(private="file") +SMALLEST_ALLOCATION_SIZE :: 2*size_of(rawptr) + +// Subdivide regions of free space into distinct circular doubly linked lists, where each linked list +// represents a range of free space blocks. The following function compute_free_list_bucket() converts +// an allocation size to the bucket index that should be looked at. +#assert(NUM_FREE_BUCKETS == 64, "Following function is tailored specifically for the NUM_FREE_BUCKETS == 64 case") +@(private="file") +compute_free_list_bucket :: proc(size: uint) -> uint { + if size < 128 { return (size >> 3) - 1 } + + clz := intrinsics.count_leading_zeros(i32(size)) + bucket_index: i32 = ((clz > 19) \ + ? 110 - (clz<<2) + ((i32)(size >> (u32)(29-clz)) ~ 4) \ + : min( 71 - (clz<<1) + ((i32)(size >> (u32)(30-clz)) ~ 2), NUM_FREE_BUCKETS-1)) + + assert(bucket_index >= 0) + assert(bucket_index < NUM_FREE_BUCKETS) + return uint(bucket_index) +} + +@(private="file") +prev_region :: proc(region: ^Region) -> ^Region { + prev_region_size := ([^]uint)(region)[-1] + prev_region_size = prev_region_size & ~uint(FREE_REGION_FLAG) + return (^Region)(uintptr(region)-uintptr(prev_region_size)) +} + +@(private="file") +next_region :: proc(region: ^Region) -> ^Region { + return (^Region)(uintptr(region)+uintptr(region.size)) +} + +@(private="file") +region_ceiling_size :: proc(region: ^Region) -> uint { + return ([^]uint)(uintptr(region)+uintptr(region.size))[-1] +} + +@(private="file") +region_is_free :: proc(r: ^Region) -> bool { + return region_ceiling_size(r) & FREE_REGION_FLAG >= 1 +} + +@(private="file") +region_is_in_use :: proc(r: ^Region) -> bool { + return r.size == region_ceiling_size(r) +} + +@(private="file") +region_payload_start_ptr :: proc(r: ^Region) -> [^]byte { + return ([^]byte)(r)[size_of(uint):] +} + +@(private="file") +region_payload_end_ptr :: proc(r: ^Region) -> [^]byte { + return ([^]byte)(r)[r.size-size_of(uint):] +} + +@(private="file") +create_used_region :: proc(ptr: rawptr, size: uint) { + assert(has_alignment(uintptr(ptr), size_of(uint))) + assert(has_alignment(size, size_of(uint))) + assert(size >= size_of(Region)) + + uptr := ([^]uint)(ptr) + uptr[0] = size + uptr[size/size_of(uint)-1] = size +} + +@(private="file") +create_free_region :: proc(ptr: rawptr, size: uint) { + assert(has_alignment(uintptr(ptr), size_of(uint))) + assert(has_alignment(size, size_of(uint))) + assert(size >= size_of(Region)) + + free_region := (^Region)(ptr) + free_region.size = size + ([^]uint)(ptr)[size/size_of(uint)-1] = size | FREE_REGION_FLAG +} + +@(private="file") +prepend_to_free_list :: proc(region: ^Region, prepend_to: ^Region) { + assert(region_is_free(region)) + region.next = prepend_to + region.prev = prepend_to.prev + prepend_to.prev = region + region.prev.next = region +} + +@(private="file") +unlink_from_free_list :: proc(region: ^Region) { + assert(region_is_free(region)) + region.prev.next = region.next + region.next.prev = region.prev +} + +@(private="file") +link_to_free_list :: proc(a: ^WASM_Allocator, free_region: ^Region) { + assert(free_region.size >= size_of(Region)) + bucket_index := compute_free_list_bucket(free_region.size-REGION_HEADER_SIZE) + free_list_head := &a.free_region_buckets[bucket_index] + free_region.prev = free_list_head + free_region.next = free_list_head.next + free_list_head.next = free_region + free_region.next.prev = free_region + a.free_region_buckets_used |= BUCKET_BITMASK_T(1) << bucket_index +} + +@(private="file") +claim_more_memory :: proc(a: ^WASM_Allocator, num_bytes: uint) -> bool { + + PAGE_SIZE :: 64 * 1024 + + page_alloc :: proc(page_count: int) -> []byte { + prev_page_count := intrinsics.wasm_memory_grow(0, uintptr(page_count)) + if prev_page_count < 0 { return nil } + + ptr := ([^]byte)(uintptr(prev_page_count) * PAGE_SIZE) + return ptr[:page_count * PAGE_SIZE] + } + + alloc :: proc(a: ^WASM_Allocator, num_bytes: uint) -> (bytes: [^]byte) #no_bounds_check { + if uint(len(a.spill)) >= num_bytes { + bytes = raw_data(a.spill[:num_bytes]) + a.spill = a.spill[num_bytes:] + return + } + + pages := int((num_bytes / PAGE_SIZE) + 1) + allocated := page_alloc(pages) + if allocated == nil { return nil } + + // If the allocated memory is a direct continuation of the spill from before, + // we can just extend the spill. + spill_end := uintptr(raw_data(a.spill)) + uintptr(len(a.spill)) + if spill_end == uintptr(raw_data(allocated)) { + raw_spill := (^Raw_Slice)(&a.spill) + raw_spill.len += len(allocated) + } else { + // Otherwise, we have to "waste" the previous spill. + // Now this is probably uncommon, and will only happen if another code path + // is also requesting pages. + a.spill = allocated + } + + bytes = raw_data(a.spill) + a.spill = a.spill[num_bytes:] + return + } + + num_bytes := num_bytes + num_bytes = align_forward(num_bytes, a.alignment) + + start_ptr := alloc(a, uint(num_bytes)) + if start_ptr == nil { return false } + + assert(has_alignment(uintptr(start_ptr), align_of(uint))) + end_ptr := start_ptr[num_bytes:] + + end_sentinel_region := (^Region)(end_ptr[-size_of(Region):]) + create_used_region(end_sentinel_region, size_of(Region)) + + // If we are the sole user of wasm_memory_grow(), it will feed us continuous/consecutive memory addresses - take advantage + // of that if so: instead of creating two disjoint memory regions blocks, expand the previous one to a larger size. + prev_alloc_end_address := a.list_of_all_regions != nil ? a.list_of_all_regions.end_ptr : nil + if start_ptr == prev_alloc_end_address { + prev_end_sentinel := prev_region((^Region)(start_ptr)) + assert(region_is_in_use(prev_end_sentinel)) + prev_region := prev_region(prev_end_sentinel) + + a.list_of_all_regions.end_ptr = end_ptr + + // Two scenarios, either the last region of the previous block was in use, in which case we need to create + // a new free region in the newly allocated space; or it was free, in which case we can extend that region + // to cover a larger size. + if region_is_free(prev_region) { + new_free_region_size := uint(uintptr(end_sentinel_region) - uintptr(prev_region)) + unlink_from_free_list(prev_region) + create_free_region(prev_region, new_free_region_size) + link_to_free_list(a, prev_region) + return true + } + + start_ptr = start_ptr[-size_of(Region):] + } else { + create_used_region(start_ptr, size_of(Region)) + + new_region_block := (^Root_Region)(start_ptr) + new_region_block.next = a.list_of_all_regions + new_region_block.end_ptr = end_ptr + a.list_of_all_regions = new_region_block + start_ptr = start_ptr[size_of(Region):] + } + + create_free_region(start_ptr, uint(uintptr(end_sentinel_region)-uintptr(start_ptr))) + link_to_free_list(a, (^Region)(start_ptr)) + return true +} + +@(private="file") +validate_alloc_size :: proc(size: uint) -> uint { + #assert(size_of(uint) >= size_of(uintptr)) + #assert(size_of(uint) % size_of(uintptr) == 0) + + // NOTE: emmalloc aligns this forward on pointer size, but I think that is a mistake and will + // do bad on wasm64p32. + + validated_size := size > SMALLEST_ALLOCATION_SIZE ? align_forward(size, size_of(uint)) : SMALLEST_ALLOCATION_SIZE + assert(validated_size >= size) // Assert we haven't wrapped. + + return validated_size +} + +@(private="file") +allocate_memory :: proc(a: ^WASM_Allocator, alignment: uint, size: uint, loc := #caller_location) -> rawptr { + + attempt_allocate :: proc(a: ^WASM_Allocator, free_region: ^Region, alignment, size: uint) -> rawptr { + assert_locked(a) + free_region := free_region + + payload_start_ptr := uintptr(region_payload_start_ptr(free_region)) + payload_start_ptr_aligned := align_forward(payload_start_ptr, uintptr(alignment)) + payload_end_ptr := uintptr(region_payload_end_ptr(free_region)) + + if payload_start_ptr_aligned + uintptr(size) > payload_end_ptr { + return nil + } + + // We have enough free space, so the memory allocation will be made into this region. Remove this free region + // from the list of free regions: whatever slop remains will be later added back to the free region pool. + unlink_from_free_list(free_region) + + // Before we proceed further, fix up the boundary between this and the preceding region, + // so that the boundary between the two regions happens at a right spot for the payload to be aligned. + if payload_start_ptr != payload_start_ptr_aligned { + prev := prev_region(free_region) + assert(region_is_in_use(prev)) + region_boundary_bump_amount := payload_start_ptr_aligned - payload_start_ptr + new_this_region_size := free_region.size - uint(region_boundary_bump_amount) + create_used_region(prev, prev.size + uint(region_boundary_bump_amount)) + free_region = (^Region)(uintptr(free_region) + region_boundary_bump_amount) + free_region.size = new_this_region_size + } + + // Next, we need to decide whether this region is so large that it should be split into two regions, + // one representing the newly used memory area, and at the high end a remaining leftover free area. + // This splitting to two is done always if there is enough space for the high end to fit a region. + // Carve 'size' bytes of payload off this region. So, + // [sz prev next sz] + // becomes + // [sz payload sz] [sz prev next sz] + if size_of(Region) + REGION_HEADER_SIZE + size <= free_region.size { + new_free_region := (^Region)(uintptr(free_region) + REGION_HEADER_SIZE + uintptr(size)) + create_free_region(new_free_region, free_region.size - size - REGION_HEADER_SIZE) + link_to_free_list(a, new_free_region) + create_used_region(free_region, size + REGION_HEADER_SIZE) + } else { + // There is not enough space to split the free memory region into used+free parts, so consume the whole + // region as used memory, not leaving a free memory region behind. + // Initialize the free region as used by resetting the ceiling size to the same value as the size at bottom. + ([^]uint)(uintptr(free_region) + uintptr(free_region.size))[-1] = free_region.size + } + + return rawptr(uintptr(free_region) + size_of(uint)) + } + + assert_locked(a) + assert(is_power_of_two(alignment)) + assert(size <= MAX_ALLOC_SIZE, "allocation too big", loc=loc) + + alignment := alignment + alignment = max(alignment, a.alignment) + + size := size + size = validate_alloc_size(size) + + // Attempt to allocate memory starting from smallest bucket that can contain the required amount of memory. + // Under normal alignment conditions this should always be the first or second bucket we look at, but if + // performing an allocation with complex alignment, we may need to look at multiple buckets. + bucket_index := compute_free_list_bucket(size) + bucket_mask := a.free_region_buckets_used >> bucket_index + + // Loop through each bucket that has free regions in it, based on bits set in free_region_buckets_used bitmap. + for bucket_mask != 0 { + index_add := intrinsics.count_trailing_zeros(bucket_mask) + bucket_index += uint(index_add) + bucket_mask >>= index_add + assert(bucket_index <= NUM_FREE_BUCKETS-1) + assert(a.free_region_buckets_used & (BUCKET_BITMASK_T(1) << bucket_index) > 0) + + free_region := a.free_region_buckets[bucket_index].next + assert(free_region != nil) + if free_region != &a.free_region_buckets[bucket_index] { + ptr := attempt_allocate(a, free_region, alignment, size) + if ptr != nil { + return ptr + } + + // We were not able to allocate from the first region found in this bucket, so penalize + // the region by cycling it to the end of the doubly circular linked list. (constant time) + // This provides a randomized guarantee that when performing allocations of size k to a + // bucket of [k-something, k+something] range, we will not always attempt to satisfy the + // allocation from the same available region at the front of the list, but we try each + // region in turn. + unlink_from_free_list(free_region) + prepend_to_free_list(free_region, &a.free_region_buckets[bucket_index]) + // But do not stick around to attempt to look at other regions in this bucket - move + // to search the next populated bucket index if this did not fit. This gives a practical + // "allocation in constant time" guarantee, since the next higher bucket will only have + // regions that are all of strictly larger size than the requested allocation. Only if + // there is a difficult alignment requirement we may fail to perform the allocation from + // a region in the next bucket, and if so, we keep trying higher buckets until one of them + // works. + bucket_index += 1 + bucket_mask >>= 1 + } else { + // This bucket was not populated after all with any regions, + // but we just had a stale bit set to mark a populated bucket. + // Reset the bit to update latest status so that we do not + // redundantly look at this bucket again. + a.free_region_buckets_used &~= BUCKET_BITMASK_T(1) << bucket_index + bucket_mask ~= 1 + } + + assert((bucket_index == NUM_FREE_BUCKETS && bucket_mask == 0) || (bucket_mask == a.free_region_buckets_used >> bucket_index)) + } + + // None of the buckets were able to accommodate an allocation. If this happens we are almost out of memory. + // The largest bucket might contain some suitable regions, but we only looked at one region in that bucket, so + // as a last resort, loop through more free regions in the bucket that represents the largest allocations available. + // But only if the bucket representing largest allocations available is not any of the first thirty buckets, + // these represent allocatable areas less than <1024 bytes - which could be a lot of scrap. + // In such case, prefer to claim more memory right away. + largest_bucket_index := NUM_FREE_BUCKETS - 1 - intrinsics.count_leading_zeros(a.free_region_buckets_used) + // free_region will be null if there is absolutely no memory left. (all buckets are 100% used) + free_region := a.free_region_buckets_used > 0 ? a.free_region_buckets[largest_bucket_index].next : nil + // The 30 first free region buckets cover memory blocks < 2048 bytes, so skip looking at those here (too small) + if a.free_region_buckets_used >> 30 > 0 { + // Look only at a constant number of regions in this bucket max, to avoid bad worst case behavior. + // If this many regions cannot find free space, we give up and prefer to claim more memory instead. + max_regions_to_try_before_giving_up :: 99 + num_tries_left := max_regions_to_try_before_giving_up + for ; free_region != &a.free_region_buckets[largest_bucket_index] && num_tries_left > 0; num_tries_left -= 1 { + ptr := attempt_allocate(a, free_region, alignment, size) + if ptr != nil { + return ptr + } + free_region = free_region.next + } + } + + // We were unable to find a free memory region. Must claim more memory! + num_bytes_to_claim := size+size_of(Region)*3 + if alignment > a.alignment { + num_bytes_to_claim += alignment + } + success := claim_more_memory(a, num_bytes_to_claim) + if (success) { + // Try allocate again with the newly available memory. + return allocate_memory(a, alignment, size) + } + + // also claim_more_memory failed, we are really really constrained :( As a last resort, go back to looking at the + // bucket we already looked at above, continuing where the above search left off - perhaps there are + // regions we overlooked the first time that might be able to satisfy the allocation. + if free_region != nil { + for free_region != &a.free_region_buckets[largest_bucket_index] { + ptr := attempt_allocate(a, free_region, alignment, size) + if ptr != nil { + return ptr + } + free_region = free_region.next + } + } + + // Fully out of memory. + return nil +} + +@(private="file") +aligned_alloc :: proc(a: ^WASM_Allocator, alignment, size: uint, loc := #caller_location) -> rawptr { + lock(a) + defer unlock(a) + + return allocate_memory(a, alignment, size, loc) +} + +@(private="file") +free :: proc(a: ^WASM_Allocator, ptr: rawptr, loc := #caller_location) { + if ptr == nil { + return + } + + region_start_ptr := uintptr(ptr) - size_of(uint) + region := (^Region)(region_start_ptr) + assert(has_alignment(region_start_ptr, size_of(uint))) + + lock(a) + defer unlock(a) + + size := region.size + assert(region_is_in_use(region), "double free or corrupt region", loc=loc) + + prev_region_size_field := ([^]uint)(region)[-1] + prev_region_size := prev_region_size_field & ~uint(FREE_REGION_FLAG) + if prev_region_size_field != prev_region_size { + prev_region := (^Region)(uintptr(region) - uintptr(prev_region_size)) + unlink_from_free_list(prev_region) + region_start_ptr = uintptr(prev_region) + size += prev_region_size + } + + next_reg := next_region(region) + size_at_end := (^uint)(region_payload_end_ptr(next_reg))^ + if next_reg.size != size_at_end { + unlink_from_free_list(next_reg) + size += next_reg.size + } + + create_free_region(rawptr(region_start_ptr), size) + link_to_free_list(a, (^Region)(region_start_ptr)) +} + +@(private="file") +aligned_realloc :: proc(a: ^WASM_Allocator, ptr: rawptr, alignment, size: uint, loc := #caller_location) -> rawptr { + + attempt_region_resize :: proc(a: ^WASM_Allocator, region: ^Region, size: uint) -> bool { + lock(a) + defer unlock(a) + + // First attempt to resize this region, if the next region that follows this one + // is a free region. + next_reg := next_region(region) + next_region_end_ptr := uintptr(next_reg) + uintptr(next_reg.size) + size_at_ceiling := ([^]uint)(next_region_end_ptr)[-1] + if next_reg.size != size_at_ceiling { // Next region is free? + assert(region_is_free(next_reg)) + new_next_region_start_ptr := uintptr(region) + uintptr(size) + assert(has_alignment(new_next_region_start_ptr, size_of(uint))) + // Next region does not shrink to too small size? + if new_next_region_start_ptr + size_of(Region) <= next_region_end_ptr { + unlink_from_free_list(next_reg) + create_free_region(rawptr(new_next_region_start_ptr), uint(next_region_end_ptr - new_next_region_start_ptr)) + link_to_free_list(a, (^Region)(new_next_region_start_ptr)) + create_used_region(region, uint(new_next_region_start_ptr - uintptr(region))) + return true + } + // If we remove the next region altogether, allocation is satisfied? + if new_next_region_start_ptr <= next_region_end_ptr { + unlink_from_free_list(next_reg) + create_used_region(region, region.size + next_reg.size) + return true + } + } else { + // Next region is an used region - we cannot change its starting address. However if we are shrinking the + // size of this region, we can create a new free region between this and the next used region. + if size + size_of(Region) <= region.size { + free_region_size := region.size - size + create_used_region(region, size) + free_region := (^Region)(uintptr(region) + uintptr(size)) + create_free_region(free_region, free_region_size) + link_to_free_list(a, free_region) + return true + } else if size <= region.size { + // Caller was asking to shrink the size, but due to not being able to fit a full Region in the shrunk + // area, we cannot actually do anything. This occurs if the shrink amount is really small. In such case, + // just call it success without doing any work. + return true + } + } + + return false + } + + if ptr == nil { + return aligned_alloc(a, alignment, size, loc) + } + + if size == 0 { + free(a, ptr, loc) + return nil + } + + if size > MAX_ALLOC_SIZE { + return nil + } + + assert(is_power_of_two(alignment)) + assert(has_alignment(uintptr(ptr), alignment), "realloc on different alignment than original allocation", loc=loc) + + size := size + size = validate_alloc_size(size) + + region := (^Region)(uintptr(ptr) - size_of(uint)) + + // Attempt an in-place resize. + if attempt_region_resize(a, region, size + REGION_HEADER_SIZE) { + return ptr + } + + // Can't do it in-place, allocate new region and copy over. + newptr := aligned_alloc(a, alignment, size, loc) + if newptr != nil { + intrinsics.mem_copy(newptr, ptr, min(size, region.size - REGION_HEADER_SIZE)) + free(a, ptr, loc=loc) + } + + return newptr +} diff --git a/bin/llvm/windows/LLVM-C.lib b/bin/llvm/windows/LLVM-C.lib index bd57594f3..0e5b6c624 100644 Binary files a/bin/llvm/windows/LLVM-C.lib and b/bin/llvm/windows/LLVM-C.lib differ diff --git a/bin/llvm/windows/clang_rt.asan-x86_64.lib b/bin/llvm/windows/clang_rt.asan-x86_64.lib index 9b6971395..0b209dc8d 100644 Binary files a/bin/llvm/windows/clang_rt.asan-x86_64.lib and b/bin/llvm/windows/clang_rt.asan-x86_64.lib differ diff --git a/build.bat b/build.bat index 050789bbc..bddde49c8 100644 --- a/build.bat +++ b/build.bat @@ -48,6 +48,9 @@ if "%2" == "1" ( set odin_version_raw="dev-%curr_year%-%curr_month%" set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF +rem Parse source code as utf-8 even on shift-jis and other codepages +rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170 +set compiler_flags= %compiler_flags% /utf-8 set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" if not exist .git\ goto skip_git_hash @@ -111,7 +114,7 @@ call build_vendor.bat if %errorlevel% neq 0 goto end_of_build rem If the demo doesn't run for you and your CPU is more than a decade old, try -microarch:native -if %release_mode% EQU 0 odin run examples/demo -- Hellope World +if %release_mode% EQU 0 odin run examples/demo -vet -strict-style -- Hellope World del *.obj > NUL 2> NUL diff --git a/build_odin.sh b/build_odin.sh index c53766290..125b9335a 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -2,7 +2,6 @@ set -eu : ${CPPFLAGS=} -: ${CXX=clang++} : ${CXXFLAGS=} : ${LDFLAGS=} : ${LLVM_CONFIG=} @@ -26,17 +25,19 @@ error() { if [ -z "$LLVM_CONFIG" ]; then # darwin, linux, openbsd - if [ -n "$(command -v llvm-config-17)" ]; then LLVM_CONFIG="llvm-config-17" + if [ -n "$(command -v llvm-config-18)" ]; then LLVM_CONFIG="llvm-config-18" + elif [ -n "$(command -v llvm-config-17)" ]; then LLVM_CONFIG="llvm-config-17" elif [ -n "$(command -v llvm-config-14)" ]; then LLVM_CONFIG="llvm-config-14" elif [ -n "$(command -v llvm-config-13)" ]; then LLVM_CONFIG="llvm-config-13" elif [ -n "$(command -v llvm-config-12)" ]; then LLVM_CONFIG="llvm-config-12" elif [ -n "$(command -v llvm-config-11)" ]; then LLVM_CONFIG="llvm-config-11" # freebsd - elif [ -n "$(command -v llvm-config17)" ]; then LLVM_CONFIG="llvm-config-17" - elif [ -n "$(command -v llvm-config14)" ]; then LLVM_CONFIG="llvm-config-14" - elif [ -n "$(command -v llvm-config13)" ]; then LLVM_CONFIG="llvm-config-13" - elif [ -n "$(command -v llvm-config12)" ]; then LLVM_CONFIG="llvm-config-12" - elif [ -n "$(command -v llvm-config11)" ]; then LLVM_CONFIG="llvm-config-11" + elif [ -n "$(command -v llvm-config18)" ]; then LLVM_CONFIG="llvm-config18" + elif [ -n "$(command -v llvm-config17)" ]; then LLVM_CONFIG="llvm-config17" + elif [ -n "$(command -v llvm-config14)" ]; then LLVM_CONFIG="llvm-config14" + elif [ -n "$(command -v llvm-config13)" ]; then LLVM_CONFIG="llvm-config13" + elif [ -n "$(command -v llvm-config12)" ]; then LLVM_CONFIG="llvm-config12" + elif [ -n "$(command -v llvm-config11)" ]; then LLVM_CONFIG="llvm-config11" # fallback elif [ -n "$(command -v llvm-config)" ]; then LLVM_CONFIG="llvm-config" else @@ -44,31 +45,51 @@ if [ -z "$LLVM_CONFIG" ]; then fi fi +if [ -x "$(which clang++)" ]; then + : ${CXX="clang++"} +elif [ -x "$($LLVM_CONFIG --bindir)/clang++" ]; then + : ${CXX=$($LLVM_CONFIG --bindir)/clang++} +else + error "No clang++ command found. Set CXX to proceed." +fi + LLVM_VERSION="$($LLVM_CONFIG --version)" LLVM_VERSION_MAJOR="$(echo $LLVM_VERSION | awk -F. '{print $1}')" LLVM_VERSION_MINOR="$(echo $LLVM_VERSION | awk -F. '{print $2}')" LLVM_VERSION_PATCH="$(echo $LLVM_VERSION | awk -F. '{print $3}')" -if [ $LLVM_VERSION_MAJOR -lt 11 ] || - ([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]); then - error "Invalid LLVM version $LLVM_VERSION: must be 11, 12, 13, 14 or 17" +if [ $LLVM_VERSION_MAJOR -lt 11 ] || ([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]) || [ $LLVM_VERSION_MAJOR -gt 18 ]; then + error "Invalid LLVM version $LLVM_VERSION: must be 11, 12, 13, 14, 17 or 18" fi case "$OS_NAME" in Darwin) if [ "$OS_ARCH" = "arm64" ]; then - if [ $LLVM_VERSION_MAJOR -lt 13 ] || [ $LLVM_VERSION_MAJOR -gt 17 ]; then - error "Darwin Arm64 requires LLVM 13, 14 or 17" + if [ $LLVM_VERSION_MAJOR -lt 13 ]; then + error "Invalid LLVM version $LLVM_VERSION: Darwin Arm64 requires LLVM 13, 14, 17 or 18" fi fi - CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" + darwin_sysroot= + if [ $(which xcrun) ]; then + darwin_sysroot="--sysroot $(xcrun --sdk macosx --show-sdk-path)" + elif [[ -e "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" ]]; then + darwin_sysroot="--sysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" + else + echo "Warning: MacOSX.sdk not found." + fi + + CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags) ${darwin_sysroot}" LDFLAGS="$LDFLAGS -liconv -ldl -framework System -lLLVM" ;; FreeBSD) CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" ;; +NetBSD) + CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" + LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" + ;; Linux) CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" LDFLAGS="$LDFLAGS -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)" @@ -123,7 +144,7 @@ build_odin() { } run_demo() { - ./odin run examples/demo/demo.odin -file -- Hellope World + ./odin run examples/demo -vet -strict-style -- Hellope World } if [ $# -eq 0 ]; then diff --git a/ci/create_nightly_json.py b/ci/create_nightly_json.py deleted file mode 100644 index 2ad086a82..000000000 --- a/ci/create_nightly_json.py +++ /dev/null @@ -1,51 +0,0 @@ -import subprocess -import sys -import json -import datetime -import urllib.parse -import sys - -def main(): - files_by_date = {} - bucket = sys.argv[1] - - files_lines = execute_cli(f"b2 ls --long {bucket} nightly").split("\n") - for x in files_lines: - parts = x.split(" ", 1) - if parts[0]: - json_str = execute_cli(f"b2 get-file-info {parts[0]}") - data = json.loads(json_str) - name = remove_prefix(data['fileName'], "nightly/") - url = f"https://f001.backblazeb2.com/file/{bucket}/nightly/{urllib.parse.quote_plus(name)}" - sha1 = data['contentSha1'] - size = int(data['size']) - ts = int(data['fileInfo']['src_last_modified_millis']) - date = datetime.datetime.fromtimestamp(ts/1000).strftime('%Y-%m-%d') - - if date not in files_by_date.keys(): - files_by_date[date] = [] - - files_by_date[date].append({ - 'name': name, - 'url': url, - 'sha1': sha1, - 'sizeInBytes': size, - }) - - now = datetime.datetime.utcnow().isoformat() - - print(json.dumps({ - 'last_updated' : now, - 'files': files_by_date - }, sort_keys=True, indent=4)) - -def remove_prefix(text, prefix): - return text[text.startswith(prefix) and len(prefix):] - -def execute_cli(command): - sb = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - return sb.stdout.read().decode("utf-8"); - -if __name__ == '__main__': - sys.exit(main()) - diff --git a/ci/delete_old_binaries.py b/ci/delete_old_binaries.py deleted file mode 100644 index 206d849f5..000000000 --- a/ci/delete_old_binaries.py +++ /dev/null @@ -1,34 +0,0 @@ -import subprocess -import sys -import json -import datetime -import urllib.parse -import sys - -def main(): - files_by_date = {} - bucket = sys.argv[1] - days_to_keep = int(sys.argv[2]) - print(f"Looking for binaries to delete older than {days_to_keep} days") - - files_lines = execute_cli(f"b2 ls --long --versions {bucket} nightly").split("\n") - for x in files_lines: - parts = [y for y in x.split(' ') if y] - - if parts and parts[0]: - date = datetime.datetime.strptime(parts[2], '%Y-%m-%d').replace(hour=0, minute=0, second=0, microsecond=0) - now = datetime.datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) - delta = now - date - - if delta.days > days_to_keep: - print(f'Deleting {parts[5]}') - execute_cli(f'b2 delete-file-version {parts[0]}') - - -def execute_cli(command): - sb = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - return sb.stdout.read().decode("utf-8"); - -if __name__ == '__main__': - sys.exit(main()) - diff --git a/ci/nightly.py b/ci/nightly.py new file mode 100644 index 000000000..7bd32899d --- /dev/null +++ b/ci/nightly.py @@ -0,0 +1,140 @@ +import os +import sys +from zipfile import ZipFile, ZIP_DEFLATED +from b2sdk.v2 import InMemoryAccountInfo, B2Api +from datetime import datetime +import json + +UPLOAD_FOLDER = "nightly/" + +info = InMemoryAccountInfo() +b2_api = B2Api(info) +application_key_id = os.environ['APPID'] +application_key = os.environ['APPKEY'] +bucket_name = os.environ['BUCKET'] +days_to_keep = os.environ['DAYS_TO_KEEP'] + +def auth() -> bool: + try: + realm = b2_api.account_info.get_realm() + return True # Already authenticated + except: + pass # Not yet authenticated + + err = b2_api.authorize_account("production", application_key_id, application_key) + return err == None + +def get_bucket(): + if not auth(): sys.exit(1) + return b2_api.get_bucket_by_name(bucket_name) + +def remove_prefix(text: str, prefix: str) -> str: + return text[text.startswith(prefix) and len(prefix):] + +def create_and_upload_artifact_zip(platform: str, artifact: str) -> int: + now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) + destination_zip_name = "odin-{}-nightly+{}.zip".format(platform, now.strftime("%Y-%m-%d")) + + source_zip_name = artifact + if not artifact.endswith(".zip"): + print(f"Creating archive {destination_zip_name} from {artifact} and uploading to {bucket_name}") + + source_zip_name = destination_zip_name + with ZipFile(source_zip_name, mode='w', compression=ZIP_DEFLATED, compresslevel=9) as z: + for root, directory, filenames in os.walk(artifact): + for file in filenames: + file_path = os.path.join(root, file) + zip_path = os.path.join("dist", os.path.relpath(file_path, artifact)) + z.write(file_path, zip_path) + + if not os.path.exists(source_zip_name): + print(f"Error: Newly created ZIP archive {source_zip_name} not found.") + return 1 + + print("Uploading {} to {}".format(source_zip_name, UPLOAD_FOLDER + destination_zip_name)) + bucket = get_bucket() + res = bucket.upload_local_file( + source_zip_name, # Local file to upload + "nightly/" + destination_zip_name, # B2 destination path + ) + return 0 + +def prune_artifacts(): + print(f"Looking for binaries to delete older than {days_to_keep} days") + + bucket = get_bucket() + for file, _ in bucket.ls(UPLOAD_FOLDER, latest_only=False): + # Timestamp is in milliseconds + date = datetime.fromtimestamp(file.upload_timestamp / 1_000.0).replace(hour=0, minute=0, second=0, microsecond=0) + now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) + delta = now - date + + if delta.days > int(days_to_keep): + print("Deleting {}".format(file.file_name)) + file.delete() + + return 0 + +def update_nightly_json(): + print(f"Updating nightly.json with files {days_to_keep} days or newer") + + files_by_date = {} + + bucket = get_bucket() + + for file, _ in bucket.ls(UPLOAD_FOLDER, latest_only=True): + # Timestamp is in milliseconds + date = datetime.fromtimestamp(file.upload_timestamp / 1_000.0).replace(hour=0, minute=0, second=0, microsecond=0).strftime('%Y-%m-%d') + name = remove_prefix(file.file_name, UPLOAD_FOLDER) + sha1 = file.content_sha1 + size = file.size + url = bucket.get_download_url(file.file_name) + + if date not in files_by_date.keys(): + files_by_date[date] = [] + + files_by_date[date].append({ + 'name': name, + 'url': url, + 'sha1': sha1, + 'sizeInBytes': size, + }) + + now = datetime.utcnow().isoformat() + + nightly = json.dumps({ + 'last_updated' : now, + 'files': files_by_date + }, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8') + + res = bucket.upload_bytes( + nightly, # JSON bytes + "nightly.json", # B2 destination path + ) + return 0 + +if __name__ == "__main__": + if len(sys.argv) == 1: + print("Usage: {} [arguments]".format(sys.argv[0])) + print("\tartifact \n\t\tCreates and uploads a platform artifact zip.") + print("\tprune\n\t\tDeletes old artifacts from bucket") + print("\tjson\n\t\tUpdate and upload nightly.json") + sys.exit(1) + else: + command = sys.argv[1].lower() + if command == "artifact": + if len(sys.argv) != 4: + print("Usage: {} artifact ".format(sys.argv[0])) + print("Error: Expected artifact command to be given platform prefix and artifact path.\n") + sys.exit(1) + + res = create_and_upload_artifact_zip(sys.argv[2], sys.argv[3]) + sys.exit(res) + + elif command == "prune": + res = prune_artifacts() + sys.exit(res) + + elif command == "json": + res = update_nightly_json() + sys.exit(res) \ No newline at end of file diff --git a/ci/upload_create_nightly.sh b/ci/upload_create_nightly.sh deleted file mode 100755 index 065cb13bf..000000000 --- a/ci/upload_create_nightly.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -set -e - -bucket=$1 -platform=$2 -artifact=$3 - -now=$(date +'%Y-%m-%d') -filename="odin-$platform-nightly+$now.zip" - -echo "Creating archive $filename from $artifact and uploading to $bucket" - -# If this is already zipped up (done before artifact upload to keep permissions in tact), just move it. -if [ "${artifact: -4}" == ".zip" ] -then - echo "Artifact already a zip" - mkdir -p "output" - mv "$artifact" "output/$filename" -else - echo "Artifact needs to be zipped" - 7z a -bd "output/$filename" -r "$artifact" -fi - -b2 upload-file --noProgress "$bucket" "output/$filename" "nightly/$filename" diff --git a/core/bufio/reader.odin b/core/bufio/reader.odin index e5307f105..a875c732d 100644 --- a/core/bufio/reader.odin +++ b/core/bufio/reader.odin @@ -29,12 +29,12 @@ MIN_READ_BUFFER_SIZE :: 16 @(private) DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128 -reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) { +reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator, loc := #caller_location) { size := size size = max(size, MIN_READ_BUFFER_SIZE) reader_reset(b, rd) b.buf_allocator = allocator - b.buf = make([]byte, size, allocator) + b.buf = make([]byte, size, allocator, loc) } reader_init_with_buf :: proc(b: ^Reader, rd: io.Reader, buf: []byte) { @@ -81,7 +81,7 @@ _reader_read_new_chunk :: proc(b: ^Reader) -> io.Error { for i := b.max_consecutive_empty_reads; i > 0; i -= 1 { n, err := io.read(b.rd, b.buf[b.w:]) if n < 0 { - return .Negative_Read + return err if err != nil else .Negative_Read } b.w += n if err != nil { @@ -189,7 +189,7 @@ reader_read :: proc(b: ^Reader, p: []byte) -> (n: int, err: io.Error) { if len(p) >= len(b.buf) { n, b.err = io.read(b.rd, p) if n < 0 { - return 0, .Negative_Read + return 0, b.err if b.err != nil else .Negative_Read } if n > 0 { @@ -202,7 +202,7 @@ reader_read :: proc(b: ^Reader, p: []byte) -> (n: int, err: io.Error) { b.r, b.w = 0, 0 n, b.err = io.read(b.rd, b.buf) if n < 0 { - return 0, .Negative_Read + return 0, b.err if b.err != nil else .Negative_Read } if n == 0 { return 0, _reader_consume_err(b) @@ -290,7 +290,7 @@ reader_write_to :: proc(b: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) { write_buf :: proc(b: ^Reader, w: io.Writer) -> (i64, io.Error) { n, err := io.write(w, b.buf[b.r:b.w]) if n < 0 { - return 0, .Negative_Write + return 0, err if err != nil else .Negative_Write } b.r += n return i64(n), err diff --git a/core/bufio/writer.odin b/core/bufio/writer.odin index 3c7fd30c5..5edd3dd6b 100644 --- a/core/bufio/writer.odin +++ b/core/bufio/writer.odin @@ -95,6 +95,10 @@ writer_write :: proc(b: ^Writer, p: []byte) -> (n: int, err: io.Error) { m: int if writer_buffered(b) == 0 { m, b.err = io.write(b.wr, p) + if m < 0 && b.err == nil { + b.err = .Negative_Write + break + } } else { m = copy(b.buf[b.n:], p) b.n += m diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index abfee6f2f..a7e9b1c64 100644 --- a/core/bytes/buffer.odin +++ b/core/bytes/buffer.odin @@ -27,19 +27,19 @@ Read_Op :: enum i8 { } -buffer_init :: proc(b: ^Buffer, buf: []byte) { - resize(&b.buf, len(buf)) +buffer_init :: proc(b: ^Buffer, buf: []byte, loc := #caller_location) { + resize(&b.buf, len(buf), loc=loc) copy(b.buf[:], buf) } -buffer_init_string :: proc(b: ^Buffer, s: string) { - resize(&b.buf, len(s)) +buffer_init_string :: proc(b: ^Buffer, s: string, loc := #caller_location) { + resize(&b.buf, len(s), loc=loc) copy(b.buf[:], s) } -buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator) { +buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator, loc := #caller_location) { if b.buf == nil { - b.buf = make([dynamic]byte, len, cap, allocator) + b.buf = make([dynamic]byte, len, cap, allocator, loc) return } @@ -96,28 +96,28 @@ buffer_truncate :: proc(b: ^Buffer, n: int) { } @(private) -_buffer_try_grow :: proc(b: ^Buffer, n: int) -> (int, bool) { +_buffer_try_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> (int, bool) { if l := len(b.buf); n <= cap(b.buf)-l { - resize(&b.buf, l+n) + resize(&b.buf, l+n, loc=loc) return l, true } return 0, false } @(private) -_buffer_grow :: proc(b: ^Buffer, n: int) -> int { +_buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> int { m := buffer_length(b) if m == 0 && b.off != 0 { buffer_reset(b) } - if i, ok := _buffer_try_grow(b, n); ok { + if i, ok := _buffer_try_grow(b, n, loc=loc); ok { return i } if b.buf == nil && n <= SMALL_BUFFER_SIZE { // Fixes #2756 by preserving allocator if already set on Buffer via init_buffer_allocator - reserve(&b.buf, SMALL_BUFFER_SIZE) - resize(&b.buf, n) + reserve(&b.buf, SMALL_BUFFER_SIZE, loc=loc) + resize(&b.buf, n, loc=loc) return 0 } @@ -127,31 +127,31 @@ _buffer_grow :: proc(b: ^Buffer, n: int) -> int { } else if c > max(int) - c - n { panic("bytes.Buffer: too large") } else { - resize(&b.buf, 2*c + n) + resize(&b.buf, 2*c + n, loc=loc) copy(b.buf[:], b.buf[b.off:]) } b.off = 0 - resize(&b.buf, m+n) + resize(&b.buf, m+n, loc=loc) return m } -buffer_grow :: proc(b: ^Buffer, n: int) { +buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) { if n < 0 { panic("bytes.buffer_grow: negative count") } - m := _buffer_grow(b, n) - resize(&b.buf, m) + m := _buffer_grow(b, n, loc=loc) + resize(&b.buf, m, loc=loc) } -buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) { +buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int, loc := #caller_location) -> (n: int, err: io.Error) { b.last_read = .Invalid if offset < 0 { err = .Invalid_Offset return } - _, ok := _buffer_try_grow(b, offset+len(p)) + _, ok := _buffer_try_grow(b, offset+len(p), loc=loc) if !ok { - _ = _buffer_grow(b, offset+len(p)) + _ = _buffer_grow(b, offset+len(p), loc=loc) } if len(b.buf) <= offset { return 0, .Short_Write @@ -160,47 +160,47 @@ buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io. } -buffer_write :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) { +buffer_write :: proc(b: ^Buffer, p: []byte, loc := #caller_location) -> (n: int, err: io.Error) { b.last_read = .Invalid - m, ok := _buffer_try_grow(b, len(p)) + m, ok := _buffer_try_grow(b, len(p), loc=loc) if !ok { - m = _buffer_grow(b, len(p)) + m = _buffer_grow(b, len(p), loc=loc) } return copy(b.buf[m:], p), nil } -buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io.Error) { - return buffer_write(b, ([^]byte)(ptr)[:size]) +buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int, loc := #caller_location) -> (n: int, err: io.Error) { + return buffer_write(b, ([^]byte)(ptr)[:size], loc=loc) } -buffer_write_string :: proc(b: ^Buffer, s: string) -> (n: int, err: io.Error) { +buffer_write_string :: proc(b: ^Buffer, s: string, loc := #caller_location) -> (n: int, err: io.Error) { b.last_read = .Invalid - m, ok := _buffer_try_grow(b, len(s)) + m, ok := _buffer_try_grow(b, len(s), loc=loc) if !ok { - m = _buffer_grow(b, len(s)) + m = _buffer_grow(b, len(s), loc=loc) } return copy(b.buf[m:], s), nil } -buffer_write_byte :: proc(b: ^Buffer, c: byte) -> io.Error { +buffer_write_byte :: proc(b: ^Buffer, c: byte, loc := #caller_location) -> io.Error { b.last_read = .Invalid - m, ok := _buffer_try_grow(b, 1) + m, ok := _buffer_try_grow(b, 1, loc=loc) if !ok { - m = _buffer_grow(b, 1) + m = _buffer_grow(b, 1, loc=loc) } b.buf[m] = c return nil } -buffer_write_rune :: proc(b: ^Buffer, r: rune) -> (n: int, err: io.Error) { +buffer_write_rune :: proc(b: ^Buffer, r: rune, loc := #caller_location) -> (n: int, err: io.Error) { if r < utf8.RUNE_SELF { - buffer_write_byte(b, byte(r)) + buffer_write_byte(b, byte(r), loc=loc) return 1, nil } b.last_read = .Invalid - m, ok := _buffer_try_grow(b, utf8.UTF_MAX) + m, ok := _buffer_try_grow(b, utf8.UTF_MAX, loc=loc) if !ok { - m = _buffer_grow(b, utf8.UTF_MAX) + m = _buffer_grow(b, utf8.UTF_MAX, loc=loc) } res: [4]byte res, n = utf8.encode_rune(r) @@ -359,7 +359,7 @@ buffer_read_from :: proc(b: ^Buffer, r: io.Reader) -> (n: i64, err: io.Error) #n resize(&b.buf, i) m, e := io.read(r, b.buf[i:cap(b.buf)]) if m < 0 { - err = .Negative_Read + err = e if e != nil else .Negative_Read return } diff --git a/core/bytes/bytes.odin b/core/bytes/bytes.odin index 208949fd8..7cbf092ac 100644 --- a/core/bytes/bytes.odin +++ b/core/bytes/bytes.odin @@ -1167,3 +1167,28 @@ fields_proc :: proc(s: []byte, f: proc(rune) -> bool, allocator := context.alloc return subslices[:] } + +// alias returns true iff a and b have a non-zero length, and any part of +// a overlaps with b. +alias :: proc "contextless" (a, b: []byte) -> bool { + a_len, b_len := len(a), len(b) + if a_len == 0 || b_len == 0 { + return false + } + + a_start, b_start := uintptr(raw_data(a)), uintptr(raw_data(b)) + a_end, b_end := a_start + uintptr(a_len-1), b_start + uintptr(b_len-1) + + return a_start <= b_end && b_start <= a_end +} + +// alias_inexactly returns true iff a and b have a non-zero length, +// the base pointer of a and b are NOT equal, and any part of a overlaps +// with b (ie: `alias(a, b)` with an exception that returns false for +// `a == b`, `b = a[:len(a)-69]` and similar conditions). +alias_inexactly :: proc "contextless" (a, b: []byte) -> bool { + if raw_data(a) == raw_data(b) { + return false + } + return alias(a, b) +} diff --git a/core/c/libc/errno.odin b/core/c/libc/errno.odin index 7af763706..d28a24f56 100644 --- a/core/c/libc/errno.odin +++ b/core/c/libc/errno.odin @@ -40,7 +40,7 @@ when ODIN_OS == .FreeBSD { ERANGE :: 34 } -when ODIN_OS == .OpenBSD { +when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { @(private="file") @(default_calling_convention="c") foreign libc { diff --git a/core/c/libc/signal.odin b/core/c/libc/signal.odin index 186b74d8c..c447e3cc3 100644 --- a/core/c/libc/signal.odin +++ b/core/c/libc/signal.odin @@ -34,20 +34,7 @@ when ODIN_OS == .Windows { SIGTERM :: 15 } -when ODIN_OS == .Linux || ODIN_OS == .FreeBSD { - SIG_ERR :: rawptr(~uintptr(0)) - SIG_DFL :: rawptr(uintptr(0)) - SIG_IGN :: rawptr(uintptr(1)) - - SIGABRT :: 6 - SIGFPE :: 8 - SIGILL :: 4 - SIGINT :: 2 - SIGSEGV :: 11 - SIGTERM :: 15 -} - -when ODIN_OS == .Darwin { +when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Darwin { SIG_ERR :: rawptr(~uintptr(0)) SIG_DFL :: rawptr(uintptr(0)) SIG_IGN :: rawptr(uintptr(1)) diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index b83ddecc8..3e1d0f5a2 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -83,7 +83,7 @@ when ODIN_OS == .Linux { } } -when ODIN_OS == .OpenBSD { +when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { fpos_t :: distinct i64 _IOFBF :: 0 @@ -102,10 +102,12 @@ when ODIN_OS == .OpenBSD { SEEK_END :: 2 foreign libc { - stderr: ^FILE - stdin: ^FILE - stdout: ^FILE + __sF: [3]FILE } + + stdin: ^FILE = &__sF[0] + stdout: ^FILE = &__sF[1] + stderr: ^FILE = &__sF[2] } when ODIN_OS == .FreeBSD { @@ -127,9 +129,9 @@ when ODIN_OS == .FreeBSD { SEEK_END :: 2 foreign libc { - stderr: ^FILE - stdin: ^FILE - stdout: ^FILE + @(link_name="__stderrp") stderr: ^FILE + @(link_name="__stdinp") stdin: ^FILE + @(link_name="__stdoutp") stdout: ^FILE } } diff --git a/core/c/libc/time.odin b/core/c/libc/time.odin index 4c4280f30..924cf4aec 100644 --- a/core/c/libc/time.odin +++ b/core/c/libc/time.odin @@ -45,7 +45,7 @@ when ODIN_OS == .Windows { } } -when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku { +when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku { @(default_calling_convention="c") foreign libc { // 7.27.2 Time manipulation functions diff --git a/core/c/libc/wctype.odin b/core/c/libc/wctype.odin index cbce220d4..a41fe7fac 100644 --- a/core/c/libc/wctype.odin +++ b/core/c/libc/wctype.odin @@ -22,7 +22,7 @@ when ODIN_OS == .Windows { wctrans_t :: distinct int wctype_t :: distinct u32 -} else when ODIN_OS == .OpenBSD { +} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { wctrans_t :: distinct rawptr wctype_t :: distinct rawptr diff --git a/core/compress/common.odin b/core/compress/common.odin index b22172e61..242538e78 100644 --- a/core/compress/common.odin +++ b/core/compress/common.odin @@ -34,13 +34,13 @@ COMPRESS_OUTPUT_ALLOCATE_MIN :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MIN, 1 << 2 */ when size_of(uintptr) == 8 { - // For 64-bit platforms, we set the default max buffer size to 4 GiB, - // which is GZIP and PKZIP's max payload size. + // For 64-bit platforms, we set the default max buffer size to 4 GiB, + // which is GZIP and PKZIP's max payload size. COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 32)) } else { // For 32-bit platforms, we set the default max buffer size to 512 MiB. - COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 29)) + COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 29)) } @@ -186,7 +186,7 @@ input_size_from_stream :: proc(z: ^Context_Stream_Input) -> (res: i64, err: Erro input_size :: proc{input_size_from_memory, input_size_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int) -> (res: []u8, err: io.Error) { #no_bounds_check { if len(z.input_data) >= size { @@ -203,7 +203,7 @@ read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int) -> (res: []u8, err: io.Error) { // TODO: REMOVE ALL USE OF context.temp_allocator here // there is literally no need for it @@ -214,13 +214,13 @@ read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int read_slice :: proc{read_slice_from_memory, read_slice_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_data :: #force_inline proc(z: ^$C, $T: typeid) -> (res: T, err: io.Error) { b := read_slice(z, size_of(T)) or_return return (^T)(&b[0])^, nil } -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_u8_from_memory :: #force_inline proc(z: ^Context_Memory_Input) -> (res: u8, err: io.Error) { #no_bounds_check { if len(z.input_data) >= 1 { @@ -232,7 +232,7 @@ read_u8_from_memory :: #force_inline proc(z: ^Context_Memory_Input) -> (res: u8, return 0, .EOF } -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_u8_from_stream :: #force_inline proc(z: ^Context_Stream_Input) -> (res: u8, err: io.Error) { b := read_slice_from_stream(z, 1) or_return return b[0], nil @@ -242,7 +242,7 @@ read_u8 :: proc{read_u8_from_memory, read_u8_from_stream} // You would typically only use this at the end of Inflate, to drain bits from the code buffer // preferentially. -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_u8_prefer_code_buffer_lsb :: #force_inline proc(z: ^$C) -> (res: u8, err: io.Error) { if z.num_bits >= 8 { res = u8(read_bits_no_refill_lsb(z, 8)) @@ -257,7 +257,7 @@ read_u8_prefer_code_buffer_lsb :: #force_inline proc(z: ^$C) -> (res: u8, err: i return } -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid) -> (res: T, err: io.Error) { size :: size_of(T) @@ -275,7 +275,7 @@ peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_data_at_offset_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) { size :: size_of(T) @@ -293,7 +293,7 @@ peek_data_at_offset_from_memory :: #force_inline proc(z: ^Context_Memory_Input, } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid) -> (res: T, err: io.Error) { size :: size_of(T) @@ -317,7 +317,7 @@ peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid return res, .None } -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_data_at_offset_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) { size :: size_of(T) @@ -352,14 +352,14 @@ peek_data :: proc{peek_data_from_memory, peek_data_from_stream, peek_data_at_off // Sliding window read back -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_back_byte :: #force_inline proc(z: ^$C, offset: i64) -> (res: u8, err: io.Error) { // Look back into the sliding window. return z.output.buf[z.bytes_written - offset], .None } // Generalized bit reader LSB -@(optimization_mode="speed") +@(optimization_mode="favor_size") refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width := i8(48)) { refill := u64(width) b := u64(0) @@ -385,7 +385,7 @@ refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width := } // Generalized bit reader LSB -@(optimization_mode="speed") +@(optimization_mode="favor_size") refill_lsb_from_stream :: proc(z: ^Context_Stream_Input, width := i8(24)) { refill := u64(width) @@ -414,13 +414,13 @@ refill_lsb_from_stream :: proc(z: ^Context_Stream_Input, width := i8(24)) { refill_lsb :: proc{refill_lsb_from_memory, refill_lsb_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") consume_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) { z.code_buffer >>= width z.num_bits -= u64(width) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") consume_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) { z.code_buffer >>= width z.num_bits -= u64(width) @@ -428,7 +428,7 @@ consume_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, wid consume_bits_lsb :: proc{consume_bits_lsb_from_memory, consume_bits_lsb_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 { if z.num_bits < u64(width) { refill_lsb(z) @@ -436,7 +436,7 @@ peek_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: return u32(z.code_buffer &~ (~u64(0) << width)) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 { if z.num_bits < u64(width) { refill_lsb(z) @@ -446,13 +446,13 @@ peek_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: peek_bits_lsb :: proc{peek_bits_lsb_from_memory, peek_bits_lsb_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 { assert(z.num_bits >= u64(width)) return u32(z.code_buffer &~ (~u64(0) << width)) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 { assert(z.num_bits >= u64(width)) return u32(z.code_buffer &~ (~u64(0) << width)) @@ -460,14 +460,14 @@ peek_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Inp peek_bits_no_refill_lsb :: proc{peek_bits_no_refill_lsb_from_memory, peek_bits_no_refill_lsb_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 { k := #force_inline peek_bits_lsb(z, width) #force_inline consume_bits_lsb(z, width) return k } -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 { k := peek_bits_lsb(z, width) consume_bits_lsb(z, width) @@ -476,14 +476,14 @@ read_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: read_bits_lsb :: proc{read_bits_lsb_from_memory, read_bits_lsb_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 { k := #force_inline peek_bits_no_refill_lsb(z, width) #force_inline consume_bits_lsb(z, width) return k } -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 { k := peek_bits_no_refill_lsb(z, width) consume_bits_lsb(z, width) @@ -493,14 +493,14 @@ read_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Inp read_bits_no_refill_lsb :: proc{read_bits_no_refill_lsb_from_memory, read_bits_no_refill_lsb_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") discard_to_next_byte_lsb_from_memory :: proc(z: ^Context_Memory_Input) { discard := u8(z.num_bits & 7) #force_inline consume_bits_lsb(z, discard) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") discard_to_next_byte_lsb_from_stream :: proc(z: ^Context_Stream_Input) { discard := u8(z.num_bits & 7) consume_bits_lsb(z, discard) diff --git a/core/compress/shoco/shoco.odin b/core/compress/shoco/shoco.odin index 3c1f412ba..269dd8875 100644 --- a/core/compress/shoco/shoco.odin +++ b/core/compress/shoco/shoco.odin @@ -98,7 +98,7 @@ decompress_slice_to_output_buffer :: proc(input: []u8, output: []u8, model := DE validate_model(model) or_return for inp < inp_end { - val := transmute(i8)input[inp] + val := i8(input[inp]) mark := int(-1) for val < 0 { @@ -274,12 +274,9 @@ compress_string_to_buffer :: proc(input: string, output: []u8, model := DEFAULT_ out_ptr := raw_data(output[out:]) switch pack.bytes_packed { - case 4: - intrinsics.unaligned_store(transmute(^u32)out_ptr, code) - case 2: - intrinsics.unaligned_store(transmute(^u16)out_ptr, u16(code)) - case 1: - intrinsics.unaligned_store(transmute(^u8)out_ptr, u8(code)) + case 4: intrinsics.unaligned_store((^u32)(out_ptr), code) + case 2: intrinsics.unaligned_store((^u16)(out_ptr), u16(code)) + case 1: intrinsics.unaligned_store( (^u8)(out_ptr), u8(code)) case: return out, .Unknown_Compression_Method } diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index b7f381f2b..c7ae9e9c8 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -120,7 +120,7 @@ Huffman_Table :: struct { } // Implementation starts here -@(optimization_mode="speed") +@(optimization_mode="favor_size") z_bit_reverse :: #force_inline proc(n: u16, bits: u8) -> (r: u16) { assert(bits <= 16) // NOTE: Can optimize with llvm.bitreverse.i64 or some bit twiddling @@ -136,7 +136,7 @@ z_bit_reverse :: #force_inline proc(n: u16, bits: u8) -> (r: u16) { } -@(optimization_mode="speed") +@(optimization_mode="favor_size") grow_buffer :: proc(buf: ^[dynamic]u8) -> (err: compress.Error) { /* That we get here at all means that we didn't pass an expected output size, @@ -154,7 +154,7 @@ grow_buffer :: proc(buf: ^[dynamic]u8) -> (err: compress.Error) { TODO: Make these return compress.Error. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") write_byte :: #force_inline proc(z: ^$C, c: u8) -> (err: io.Error) #no_bounds_check { /* Resize if needed. @@ -173,7 +173,7 @@ write_byte :: #force_inline proc(z: ^$C, c: u8) -> (err: io.Error) #no_bounds_ch return .None } -@(optimization_mode="speed") +@(optimization_mode="favor_size") repl_byte :: proc(z: ^$C, count: u16, c: u8) -> (err: io.Error) #no_bounds_check { /* TODO(Jeroen): Once we have a magic ring buffer, we can just peek/write into it @@ -201,7 +201,7 @@ repl_byte :: proc(z: ^$C, count: u16, c: u8) -> (err: io.Error) #no_bounds_check return .None } -@(optimization_mode="speed") +@(optimization_mode="favor_size") repl_bytes :: proc(z: ^$C, count: u16, distance: u16) -> (err: io.Error) { /* TODO(Jeroen): Once we have a magic ring buffer, we can just peek/write into it @@ -234,8 +234,8 @@ allocate_huffman_table :: proc(allocator := context.allocator) -> (z: ^Huffman_T return new(Huffman_Table, allocator), nil } -@(optimization_mode="speed") -build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) { +@(optimization_mode="favor_size") +build_huffman :: #force_no_inline proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) { sizes: [HUFFMAN_MAX_BITS+1]int next_code: [HUFFMAN_MAX_BITS+1]int @@ -293,7 +293,7 @@ build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) { return nil } -@(optimization_mode="speed") +@(optimization_mode="favor_size") decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check { code := u16(compress.peek_bits_lsb(z,16)) @@ -324,7 +324,7 @@ decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Erro return r, nil } -@(optimization_mode="speed") +@(optimization_mode="favor_size") decode_huffman :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check { if z.num_bits < 16 { if z.num_bits > 63 { @@ -344,7 +344,7 @@ decode_huffman :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bo return decode_huffman_slowpath(z, t) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err: Error) #no_bounds_check { #no_bounds_check for { value, e := decode_huffman(z, z_repeat) @@ -413,7 +413,7 @@ parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err: } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := false, expected_output_size := -1, allocator := context.allocator) -> (err: Error) #no_bounds_check { /* ctx.output must be a bytes.Buffer for now. We'll add a separate implementation that writes to a stream. @@ -486,7 +486,7 @@ inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := f // TODO: Check alignment of reserve/resize. -@(optimization_mode="speed") +@(optimization_mode="favor_size") inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.allocator) -> (err: Error) #no_bounds_check { context.allocator = allocator expected_output_size := expected_output_size @@ -670,4 +670,4 @@ inflate_from_byte_array_raw :: proc(input: []u8, buf: ^bytes.Buffer, raw := fals return inflate_raw(&ctx, expected_output_size=expected_output_size) } -inflate :: proc{inflate_from_context, inflate_from_byte_array} +inflate :: proc{inflate_from_context, inflate_from_byte_array} \ No newline at end of file diff --git a/core/container/avl/avl.odin b/core/container/avl/avl.odin index eecc1b756..8a9d1f3d9 100644 --- a/core/container/avl/avl.odin +++ b/core/container/avl/avl.odin @@ -5,13 +5,10 @@ The implementation is non-intrusive, and non-recursive. */ package container_avl -import "base:intrinsics" -import "base:runtime" +@(require) import "base:intrinsics" +@(require) import "base:runtime" import "core:slice" -_ :: intrinsics -_ :: runtime - // Originally based on the CC0 implementation by Eric Biggers // See: https://github.com/ebiggers/avl_tree/ @@ -90,7 +87,7 @@ init_cmp :: proc( init_ordered :: proc( t: ^$T/Tree($Value), node_allocator := context.allocator, -) where intrinsics.type_is_ordered_numeric(Value) { +) where intrinsics.type_is_ordered(Value) { init_cmp(t, slice.cmp_proc(Value), node_allocator) } @@ -675,4 +672,4 @@ iterator_first :: proc "contextless" (it: ^Iterator($Value)) { if it._cur != nil { it._next = node_next_or_prev_in_order(it._cur, it._direction) } -} +} \ No newline at end of file diff --git a/core/container/bit_array/bit_array.odin b/core/container/bit_array/bit_array.odin index a8720715c..b53bacda7 100644 --- a/core/container/bit_array/bit_array.odin +++ b/core/container/bit_array/bit_array.odin @@ -210,8 +210,11 @@ set :: proc(ba: ^Bit_Array, #any_int index: uint, set_to: bool = true, allocator ba.max_index = max(idx, ba.max_index) - if set_to{ ba.bits[leg_index] |= 1 << uint(bit_index) } - else { ba.bits[leg_index] &= ~(1 << uint(bit_index)) } + if set_to { + ba.bits[leg_index] |= 1 << uint(bit_index) + } else { + ba.bits[leg_index] &~= 1 << uint(bit_index) + } return true } @@ -253,7 +256,7 @@ Inputs: - index: Which bit in the array */ unsafe_unset :: proc(b: ^Bit_Array, bit: int) #no_bounds_check { - b.bits[bit >> INDEX_SHIFT] &= ~(1 << uint(bit & INDEX_MASK)) + b.bits[bit >> INDEX_SHIFT] &~= 1 << uint(bit & INDEX_MASK) } /* A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative). diff --git a/core/container/intrusive/list/doc.odin b/core/container/intrusive/list/doc.odin new file mode 100644 index 000000000..1a5a12f49 --- /dev/null +++ b/core/container/intrusive/list/doc.odin @@ -0,0 +1,46 @@ +/* +Package list implements an intrusive doubly-linked list. + +An intrusive container requires a `Node` to be embedded in your own structure, like this: + + My_String :: struct { + node: list.Node, + value: string, + } + +Embedding the members of a `list.Node` in your structure with the `using` keyword is also allowed: + + My_String :: struct { + using node: list.Node, + value: string, + } + +Here is a full example: + + package test + + import "core:fmt" + import "core:container/intrusive/list" + + main :: proc() { + l: list.List + + one := My_String{value="Hello"} + two := My_String{value="World"} + + list.push_back(&l, &one.node) + list.push_back(&l, &two.node) + + iter := list.iterator_head(l, My_String, "node") + for s in list.iterate_next(&iter) { + fmt.println(s.value) + } + } + + My_String :: struct { + node: list.Node, + value: string, + } + +*/ +package container_intrusive_list diff --git a/core/container/intrusive/list/intrusive_list.odin b/core/container/intrusive/list/intrusive_list.odin index 1a3175002..5b29efb22 100644 --- a/core/container/intrusive/list/intrusive_list.odin +++ b/core/container/intrusive/list/intrusive_list.odin @@ -18,11 +18,18 @@ List :: struct { tail: ^Node, } - +// The list link you must include in your own structure. Node :: struct { prev, next: ^Node, } +/* +Inserts a new element at the front of the list with O(1) time complexity. + +**Inputs** +- list: The container list +- node: The node member of the user-defined element structure +*/ push_front :: proc "contextless" (list: ^List, node: ^Node) { if list.head != nil { list.head.prev = node @@ -33,7 +40,13 @@ push_front :: proc "contextless" (list: ^List, node: ^Node) { node.prev, node.next = nil, nil } } +/* +Inserts a new element at the back of the list with O(1) time complexity. +**Inputs** +- list: The container list +- node: The node member of the user-defined element structure +*/ push_back :: proc "contextless" (list: ^List, node: ^Node) { if list.tail != nil { list.tail.next = node @@ -45,6 +58,13 @@ push_back :: proc "contextless" (list: ^List, node: ^Node) { } } +/* +Removes an element from a list with O(1) time complexity. + +**Inputs** +- list: The container list +- node: The node member of the user-defined element structure to be removed +*/ remove :: proc "contextless" (list: ^List, node: ^Node) { if node != nil { if node.next != nil { @@ -61,7 +81,13 @@ remove :: proc "contextless" (list: ^List, node: ^Node) { } } } +/* +Removes from the given list all elements that satisfy a condition with O(N) time complexity. +**Inputs** +- list: The container list +- to_erase: The condition procedure. It should return `true` if a node should be removed, `false` otherwise +*/ remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) { for node := list.head; node != nil; { next := node.next @@ -82,7 +108,13 @@ remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) { node = next } } +/* +Removes from the given list all elements that satisfy a condition with O(N) time complexity. +**Inputs** +- list: The container list +- to_erase: The _contextless_ condition procedure. It should return `true` if a node should be removed, `false` otherwise +*/ remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^Node) -> bool) { for node := list.head; node != nil; { next := node.next @@ -104,12 +136,26 @@ remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^N } } +/* +Checks whether the given list does not contain any element. +**Inputs** +- list: The container list +**Returns** `true` if `list` is empty, `false` otherwise +*/ is_empty :: proc "contextless" (list: ^List) -> bool { return list.head == nil } +/* +Removes and returns the element at the front of the list with O(1) time complexity. + +**Inputs** +- list: The container list + +**Returns** The node member of the user-defined element structure, or `nil` if the list is empty +*/ pop_front :: proc "contextless" (list: ^List) -> ^Node { link := list.head if link == nil { @@ -130,6 +176,14 @@ pop_front :: proc "contextless" (list: ^List) -> ^Node { return link } +/* +Removes and returns the element at the back of the list with O(1) time complexity. + +**Inputs** +- list: The container list + +**Returns** The node member of the user-defined element structure, or `nil` if the list is empty +*/ pop_back :: proc "contextless" (list: ^List) -> ^Node { link := list.tail if link == nil { @@ -151,29 +205,102 @@ pop_back :: proc "contextless" (list: ^List) -> ^Node { } + Iterator :: struct($T: typeid) { curr: ^Node, offset: uintptr, } +/* +Creates an iterator pointing at the head of the given list. For an example, see `iterate_next`. + +**Inputs** +- list: The container list +- T: The type of the list's elements +- field_name: The name of the node field in the `T` structure + +**Returns** An iterator pointing at the head of `list` + +*/ iterator_head :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T) where intrinsics.type_has_field(T, field_name), intrinsics.type_field_type(T, field_name) == Node { return {list.head, offset_of_by_string(T, field_name)} } +/* +Creates an iterator pointing at the tail of the given list. For an example, see `iterate_prev`. +**Inputs** +- list: The container list +- T: The type of the list's elements +- field_name: The name of the node field in the `T` structure + +**Returns** An iterator pointing at the tail of `list` + +*/ iterator_tail :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T) where intrinsics.type_has_field(T, field_name), intrinsics.type_field_type(T, field_name) == Node { return {list.tail, offset_of_by_string(T, field_name)} } +/* +Creates an iterator pointing at the specified node of a list. +**Inputs** +- node: a list node +- T: The type of the list's elements +- field_name: The name of the node field in the `T` structure + +**Returns** An iterator pointing at `node` + +*/ iterator_from_node :: proc "contextless" (node: ^Node, $T: typeid, $field_name: string) -> Iterator(T) where intrinsics.type_has_field(T, field_name), intrinsics.type_field_type(T, field_name) == Node { return {node, offset_of_by_string(T, field_name)} } +/* +Retrieves the next element in a list and advances the iterator. + +**Inputs** +- it: The iterator + +**Returns** +- ptr: The next list element +- ok: `true` if the element is valid (the iterator could advance), `false` otherwise + +Example: + + import "core:fmt" + import "core:container/intrusive/list" + + iterate_next_example :: proc() { + l: list.List + + one := My_Struct{value=1} + two := My_Struct{value=2} + + list.push_back(&l, &one.node) + list.push_back(&l, &two.node) + + it := list.iterator_head(l, My_Struct, "node") + for num in list.iterate_next(&it) { + fmt.println(num.value) + } + } + + My_Struct :: struct { + node : list.Node, + value: int, + } + +Output: + + 1 + 2 + +*/ iterate_next :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { node := it.curr if node == nil { @@ -183,7 +310,47 @@ iterate_next :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { return (^T)(uintptr(node) - it.offset), true } +/* +Retrieves the previous element in a list and recede the iterator. +**Inputs** +- it: The iterator + +**Returns** +- ptr: The previous list element +- ok: `true` if the element is valid (the iterator could recede), `false` otherwise + +Example: + + import "core:fmt" + import "core:container/intrusive/list" + + iterate_next_example :: proc() { + l: list.List + + one := My_Struct{value=1} + two := My_Struct{value=2} + + list.push_back(&l, &one.node) + list.push_back(&l, &two.node) + + it := list.iterator_tail(l, My_Struct, "node") + for num in list.iterate_prev(&it) { + fmt.println(num.value) + } + } + + My_Struct :: struct { + node : list.Node, + value: int, + } + +Output: + + 2 + 1 + +*/ iterate_prev :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { node := it.curr if node == nil { @@ -192,4 +359,4 @@ iterate_prev :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { it.curr = node.prev return (^T)(uintptr(node) - it.offset), true -} \ No newline at end of file +} diff --git a/core/container/lru/lru_cache.odin b/core/container/lru/lru_cache.odin index 23f01fac3..f8aa55dc2 100644 --- a/core/container/lru/lru_cache.odin +++ b/core/container/lru/lru_cache.odin @@ -70,8 +70,7 @@ set :: proc(c: ^$C/Cache($Key, $Value), key: Key, value: Value) -> runtime.Alloc if c.count == c.capacity { e = c.tail _remove_node(c, e) - } - else { + } else { c.count += 1 e = new(Node(Key, Value), c.node_allocator) or_return } diff --git a/core/container/queue/queue.odin b/core/container/queue/queue.odin index e46dccb33..f83a5f2b7 100644 --- a/core/container/queue/queue.odin +++ b/core/container/queue/queue.odin @@ -95,11 +95,11 @@ front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T { } back :: proc(q: ^$Q/Queue($T)) -> T { - idx := (q.offset+uint(q.len))%builtin.len(q.data) + idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) return q.data[idx] } back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T { - idx := (q.offset+uint(q.len))%builtin.len(q.data) + idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) return &q.data[idx] } @@ -189,7 +189,7 @@ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { return } -// Push multiple elements to the front of the queue +// Push multiple elements to the back of the queue push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error) { n := uint(builtin.len(elems)) if space(q^) < int(n) { @@ -241,7 +241,7 @@ clear :: proc(q: ^$Q/Queue($T)) { } -// Internal growinh procedure +// Internal growing procedure _grow :: proc(q: ^$Q/Queue($T), min_capacity: uint = 0) -> runtime.Allocator_Error { new_capacity := max(min_capacity, uint(8), uint(builtin.len(q.data))*2) n := uint(builtin.len(q.data)) diff --git a/core/container/rbtree/rbtree.odin b/core/container/rbtree/rbtree.odin new file mode 100644 index 000000000..090551367 --- /dev/null +++ b/core/container/rbtree/rbtree.odin @@ -0,0 +1,568 @@ +// This package implements a red-black tree +package container_rbtree + +@(require) import "base:intrinsics" +@(require) import "base:runtime" +import "core:slice" + +// Originally based on the CC0 implementation from literateprograms.org +// But with API design mimicking `core:container/avl` for ease of use. + +// Direction specifies the traversal direction for a tree iterator. +Direction :: enum i8 { + // Backward is the in-order backwards direction. + Backward = -1, + // Forward is the in-order forwards direction. + Forward = 1, +} + +Ordering :: slice.Ordering + +// Tree is a red-black tree +Tree :: struct($Key: typeid, $Value: typeid) { + // user_data is a parameter that will be passed to the on_remove + // callback. + user_data: rawptr, + // on_remove is an optional callback that can be called immediately + // after a node is removed from the tree. + on_remove: proc(key: Key, value: Value, user_data: rawptr), + + _root: ^Node(Key, Value), + _node_allocator: runtime.Allocator, + _cmp_fn: proc(Key, Key) -> Ordering, + _size: int, +} + +// Node is a red-black tree node. +// +// WARNING: It is unsafe to mutate value if the node is part of a tree +// if doing so will alter the Node's sort position relative to other +// elements in the tree. +Node :: struct($Key: typeid, $Value: typeid) { + key: Key, + value: Value, + + _parent: ^Node(Key, Value), + _left: ^Node(Key, Value), + _right: ^Node(Key, Value), + _color: Color, +} + +// Might store this in the node pointer in the future, but that'll require a decent amount of rework to pass ^^N instead of ^N +Color :: enum uintptr {Black = 0, Red = 1} + +// Iterator is a tree iterator. +// +// WARNING: It is unsafe to modify the tree while iterating, except via +// the iterator_remove method. +Iterator :: struct($Key: typeid, $Value: typeid) { + _tree: ^Tree(Key, Value), + _cur: ^Node(Key, Value), + _next: ^Node(Key, Value), + _direction: Direction, + _called_next: bool, +} + +// init initializes a tree. +init :: proc { + init_ordered, + init_cmp, +} + +// init_cmp initializes a tree. +init_cmp :: proc(t: ^$T/Tree($Key, $Value), cmp_fn: proc(a, b: Key) -> Ordering, node_allocator := context.allocator) { + t._root = nil + t._node_allocator = node_allocator + t._cmp_fn = cmp_fn + t._size = 0 +} + +// init_ordered initializes a tree containing ordered keys, with +// a comparison function that results in an ascending order sort. +init_ordered :: proc(t: ^$T/Tree($Key, $Value), node_allocator := context.allocator) where intrinsics.type_is_ordered(Key) { + init_cmp(t, slice.cmp_proc(Key), node_allocator) +} + +// destroy de-initializes a tree. +destroy :: proc(t: ^$T/Tree($Key, $Value), call_on_remove: bool = true) { + iter := iterator(t, .Forward) + for _ in iterator_next(&iter) { + iterator_remove(&iter, call_on_remove) + } +} + +len :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> (node_count: int) { + return t._size +} + +// first returns the first node in the tree (in-order) or nil iff +// the tree is empty. +first :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) { + return tree_first_or_last_in_order(t, Direction.Backward) +} + +// last returns the last element in the tree (in-order) or nil iff +// the tree is empty. +last :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) { + return tree_first_or_last_in_order(t, Direction.Forward) +} + +// find finds the key in the tree, and returns the corresponding node, or nil iff the value is not present. +find :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) { + node = t._root + for node != nil { + switch t._cmp_fn(key, node.key) { + case .Equal: return node + case .Less: node = node._left + case .Greater: node = node._right + } + } + return node +} + +// find_value finds the key in the tree, and returns the corresponding value, or nil iff the value is not present. +find_value :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (value: Value, ok: bool) #optional_ok { + if n := find(t, key); n != nil { + return n.value, true + } + return +} + +// find_or_insert attempts to insert the value into the tree, and returns +// the node, a boolean indicating if the value was inserted, and the +// node allocator error if relevant. If the value is already present, the existing node is updated. +find_or_insert :: proc(t: ^$T/Tree($Key, $Value), key: Key, value: Value) -> (n: ^Node(Key, Value), inserted: bool, err: runtime.Allocator_Error) { + n_ptr := &t._root + for n_ptr^ != nil { + n = n_ptr^ + switch t._cmp_fn(key, n.key) { + case .Less: + n_ptr = &n._left + case .Greater: + n_ptr = &n._right + case .Equal: + return + } + } + _parent := n + + n = new_clone(Node(Key, Value){key=key, value=value, _parent=_parent, _color=.Red}, t._node_allocator) or_return + n_ptr^ = n + insert_case1(t, n) + t._size += 1 + return n, true, nil +} + +// remove removes a node or value from the tree, and returns true iff the +// removal was successful. While the node's value will be left intact, +// the node itself will be freed via the tree's node allocator. +remove :: proc { + remove_key, + remove_node, +} + +// remove_value removes a value from the tree, and returns true iff the +// removal was successful. While the node's key + value will be left intact, +// the node itself will be freed via the tree's node allocator. +remove_key :: proc(t: ^$T/Tree($Key, $Value), key: Key, call_on_remove := true) -> bool { + n := find(t, key) + if n == nil { + return false // Key not found, nothing to do + } + return remove_node(t, n, call_on_remove) +} + +// remove_node removes a node from the tree, and returns true iff the +// removal was successful. While the node's key + value will be left intact, +// the node itself will be freed via the tree's node allocator. +remove_node :: proc(t: ^$T/Tree($Key, $Value), node: ^$N/Node(Key, Value), call_on_remove := true) -> (found: bool) { + if node._parent == node || (node._parent == nil && t._root != node) { + return false // Don't touch self-parented or dangling nodes. + } + node := node + if node._left != nil && node._right != nil { + // Copy key + value from predecessor and delete it instead + predecessor := maximum_node(node._left) + node.key = predecessor.key + node.value = predecessor.value + node = predecessor + } + + child := node._right == nil ? node._left : node._right + if node_color(node) == .Black { + node._color = node_color(child) + remove_case1(t, node) + } + replace_node(t, node, child) + if node._parent == nil && child != nil { + child._color = .Black // root should be black + } + + if call_on_remove && t.on_remove != nil { + t.on_remove(node.key, node.value, t.user_data) + } + free(node, t._node_allocator) + t._size -= 1 + return true +} + +// iterator returns a tree iterator in the specified direction. +iterator :: proc "contextless" (t: ^$T/Tree($Key, $Value), direction: Direction) -> Iterator(Key, Value) { + it: Iterator(Key, Value) + it._tree = cast(^Tree(Key, Value))t + it._direction = direction + + iterator_first(&it) + + return it +} + +// iterator_from_pos returns a tree iterator in the specified direction, +// spanning the range [pos, last] (inclusive). +iterator_from_pos :: proc "contextless" (t: ^$T/Tree($Key, $Value), pos: ^Node(Key, Value), direction: Direction) -> Iterator(Key, Value) { + it: Iterator(Key, Value) + it._tree = transmute(^Tree(Key, Value))t + it._direction = direction + it._next = nil + it._called_next = false + + if it._cur = pos; pos != nil { + it._next = node_next_or_prev_in_order(it._cur, it._direction) + } + + return it +} + +// iterator_get returns the node currently pointed to by the iterator, +// or nil iff the node has been removed, the tree is empty, or the end +// of the tree has been reached. +iterator_get :: proc "contextless" (it: ^$I/Iterator($Key, $Value)) -> ^Node(Key, Value) { + return it._cur +} + +// iterator_remove removes the node currently pointed to by the iterator, +// and returns true iff the removal was successful. Semantics are the +// same as the Tree remove. +iterator_remove :: proc(it: ^$I/Iterator($Key, $Value), call_on_remove: bool = true) -> bool { + if it._cur == nil { + return false + } + + ok := remove_node(it._tree, it._cur , call_on_remove) + if ok { + it._cur = nil + } + + return ok +} + +// iterator_next advances the iterator and returns the (node, true) or +// or (nil, false) iff the end of the tree has been reached. +// +// Note: The first call to iterator_next will return the first node instead +// of advancing the iterator. +iterator_next :: proc "contextless" (it: ^$I/Iterator($Key, $Value)) -> (^Node(Key, Value), bool) { + // This check is needed so that the first element gets returned from + // a brand-new iterator, and so that the somewhat contrived case where + // iterator_remove is called before the first call to iterator_next + // returns the correct value. + if !it._called_next { + it._called_next = true + + // There can be the contrived case where iterator_remove is + // called before ever calling iterator_next, which needs to be + // handled as an actual call to next. + // + // If this happens it._cur will be nil, so only return the + // first value, if it._cur is valid. + if it._cur != nil { + return it._cur, true + } + } + + if it._next == nil { + return nil, false + } + + it._cur = it._next + it._next = node_next_or_prev_in_order(it._cur, it._direction) + + return it._cur, true +} + +@(private) +tree_first_or_last_in_order :: proc "contextless" (t: ^$T/Tree($Key, $Value), direction: Direction) -> ^Node(Key, Value) { + first, sign := t._root, i8(direction) + if first != nil { + for { + tmp := node_get_child(first, sign) + if tmp == nil { + break + } + first = tmp + } + } + return first +} + +@(private) +node_get_child :: #force_inline proc "contextless" (n: ^Node($Key, $Value), sign: i8) -> ^Node(Key, Value) { + if sign < 0 { + return n._left + } + return n._right +} + +@(private) +node_next_or_prev_in_order :: proc "contextless" (n: ^Node($Key, $Value), direction: Direction) -> ^Node(Key, Value) { + next, tmp: ^Node(Key, Value) + sign := i8(direction) + + if next = node_get_child(n, +sign); next != nil { + for { + tmp = node_get_child(next, -sign) + if tmp == nil { + break + } + next = tmp + } + } else { + tmp, next = n, n._parent + for next != nil && tmp == node_get_child(next, +sign) { + tmp, next = next, next._parent + } + } + return next +} + +@(private) +iterator_first :: proc "contextless" (it: ^Iterator($Key, $Value)) { + // This is private because behavior when the user manually calls + // iterator_first followed by iterator_next is unintuitive, since + // the first call to iterator_next MUST return the first node + // instead of advancing so that `for node in iterator_next(&next)` + // works as expected. + + switch it._direction { + case .Forward: + it._cur = tree_first_or_last_in_order(it._tree, .Backward) + case .Backward: + it._cur = tree_first_or_last_in_order(it._tree, .Forward) + } + + it._next = nil + it._called_next = false + + if it._cur != nil { + it._next = node_next_or_prev_in_order(it._cur, it._direction) + } +} + +@(private) +grand_parent :: proc(n: ^$N/Node($Key, $Value)) -> (g: ^N) { + return n._parent._parent +} + +@(private) +sibling :: proc(n: ^$N/Node($Key, $Value)) -> (s: ^N) { + if n == n._parent._left { + return n._parent._right + } else { + return n._parent._left + } +} + +@(private) +uncle :: proc(n: ^$N/Node($Key, $Value)) -> (u: ^N) { + return sibling(n._parent) +} + +@(private) +rotate__left :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) { + r := n._right + replace_node(t, n, r) + n._right = r._left + if r._left != nil { + r._left._parent = n + } + r._left = n + n._parent = r +} + +@(private) +rotate__right :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) { + l := n._left + replace_node(t, n, l) + n._left = l._right + if l._right != nil { + l._right._parent = n + } + l._right = n + n._parent = l +} + +@(private) +replace_node :: proc(t: ^$T/Tree($Key, $Value), old_n: ^$N/Node(Key, Value), new_n: ^N) { + if old_n._parent == nil { + t._root = new_n + } else { + if (old_n == old_n._parent._left) { + old_n._parent._left = new_n + } else { + old_n._parent._right = new_n + } + } + if new_n != nil { + new_n._parent = old_n._parent + } +} + +@(private) +insert_case1 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) { + if n._parent == nil { + n._color = .Black + } else { + insert_case2(t, n) + } +} + +@(private) +insert_case2 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) { + if node_color(n._parent) == .Black { + return // Tree is still valid + } else { + insert_case3(t, n) + } +} + +@(private) +insert_case3 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) { + if node_color(uncle(n)) == .Red { + n._parent._color = .Black + uncle(n)._color = .Black + grand_parent(n)._color = .Red + insert_case1(t, grand_parent(n)) + } else { + insert_case4(t, n) + } +} + +@(private) +insert_case4 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) { + n := n + if n == n._parent._right && n._parent == grand_parent(n)._left { + rotate__left(t, n._parent) + n = n._left + } else if n == n._parent._left && n._parent == grand_parent(n)._right { + rotate__right(t, n._parent) + n = n._right + } + insert_case5(t, n) +} + +@(private) +insert_case5 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) { + n._parent._color = .Black + grand_parent(n)._color = .Red + if n == n._parent._left && n._parent == grand_parent(n)._left { + rotate__right(t, grand_parent(n)) + } else { + rotate__left(t, grand_parent(n)) + } +} + +// The maximum_node() helper function just walks _right until it reaches the last non-leaf: +@(private) +maximum_node :: proc(n: ^$N/Node($Key, $Value)) -> (max_node: ^N) { + n := n + for n._right != nil { + n = n._right + } + return n +} + +@(private) +remove_case1 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) { + if n._parent == nil { + return + } else { + remove_case2(t, n) + } +} + +@(private) +remove_case2 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) { + if node_color(sibling(n)) == .Red { + n._parent._color = .Red + sibling(n)._color = .Black + if n == n._parent._left { + rotate__left(t, n._parent) + } else { + rotate__right(t, n._parent) + } + } + remove_case3(t, n) +} + +@(private) +remove_case3 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) { + if node_color(n._parent) == .Black && + node_color(sibling(n)) == .Black && + node_color(sibling(n)._left) == .Black && + node_color(sibling(n)._right) == .Black { + sibling(n)._color = .Red + remove_case1(t, n._parent) + } else { + remove_case4(t, n) + } +} + +@(private) +remove_case4 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) { + if node_color(n._parent) == .Red && + node_color(sibling(n)) == .Black && + node_color(sibling(n)._left) == .Black && + node_color(sibling(n)._right) == .Black { + sibling(n)._color = .Red + n._parent._color = .Black + } else { + remove_case5(t, n) + } +} + +@(private) +remove_case5 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) { + if n == n._parent._left && + node_color(sibling(n)) == .Black && + node_color(sibling(n)._left) == .Red && + node_color(sibling(n)._right) == .Black { + sibling(n)._color = .Red + sibling(n)._left._color = .Black + rotate__right(t, sibling(n)) + } else if n == n._parent._right && + node_color(sibling(n)) == .Black && + node_color(sibling(n)._right) == .Red && + node_color(sibling(n)._left) == .Black { + sibling(n)._color = .Red + sibling(n)._right._color = .Black + rotate__left(t, sibling(n)) + } + remove_case6(t, n) +} + +@(private) +remove_case6 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) { + sibling(n)._color = node_color(n._parent) + n._parent._color = .Black + if n == n._parent._left { + sibling(n)._right._color = .Black + rotate__left(t, n._parent) + } else { + sibling(n)._left._color = .Black + rotate__right(t, n._parent) + } +} + +node_color :: proc(n: ^$N/Node($Key, $Value)) -> (c: Color) { + return n == nil ? .Black : n._color +} \ No newline at end of file diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin index ecec7b80c..b2068469d 100644 --- a/core/container/small_array/small_array.odin +++ b/core/container/small_array/small_array.odin @@ -119,20 +119,20 @@ consume :: proc "odin" (a: ^$A/Small_Array($N, $T), count: int, loc := #caller_l } ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check { - runtime.bounds_check_error_loc(loc, index, a.len) - if index+1 < a.len { + runtime.bounds_check_error_loc(loc, index, a.len) + if index+1 < a.len { copy(a.data[index:], a.data[index+1:]) } a.len -= 1 } unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check { - runtime.bounds_check_error_loc(loc, index, a.len) + runtime.bounds_check_error_loc(loc, index, a.len) n := a.len-1 if index != n { a.data[index] = a.data[n] } - a.len -= 1 + a.len -= 1 } clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) { diff --git a/core/container/topological_sort/topological_sort.odin b/core/container/topological_sort/topological_sort.odin index 0d34e8d02..10765958e 100644 --- a/core/container/topological_sort/topological_sort.odin +++ b/core/container/topological_sort/topological_sort.odin @@ -61,7 +61,7 @@ add_dependency :: proc(sorter: ^$S/Sorter($K), key, dependency: K) -> bool { } find.dependents[key] = true - find = &sorter.relations[key] + find = &sorter.relations[key] if find == nil { find = map_insert(&sorter.relations, key, make_relations(sorter)) } diff --git a/core/crypto/_aes/aes.odin b/core/crypto/_aes/aes.odin new file mode 100644 index 000000000..4f52485d2 --- /dev/null +++ b/core/crypto/_aes/aes.odin @@ -0,0 +1,28 @@ +package _aes + +// KEY_SIZE_128 is the AES-128 key size in bytes. +KEY_SIZE_128 :: 16 +// KEY_SIZE_192 is the AES-192 key size in bytes. +KEY_SIZE_192 :: 24 +// KEY_SIZE_256 is the AES-256 key size in bytes. +KEY_SIZE_256 :: 32 + +// BLOCK_SIZE is the AES block size in bytes. +BLOCK_SIZE :: 16 + +// ROUNDS_128 is the number of rounds for AES-128. +ROUNDS_128 :: 10 +// ROUNDS_192 is the number of rounds for AES-192. +ROUNDS_192 :: 12 +// ROUNDS_256 is the number of rounds for AES-256. +ROUNDS_256 :: 14 + +// GHASH_KEY_SIZE is the GHASH key size in bytes. +GHASH_KEY_SIZE :: 16 +// GHASH_BLOCK_SIZE is the GHASH block size in bytes. +GHASH_BLOCK_SIZE :: 16 +// GHASH_TAG_SIZE is the GHASH tag size in bytes. +GHASH_TAG_SIZE :: 16 + +// RCON is the AES keyschedule round constants. +RCON := [10]byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36} diff --git a/core/crypto/_aes/ct64/api.odin b/core/crypto/_aes/ct64/api.odin new file mode 100644 index 000000000..f57a630b1 --- /dev/null +++ b/core/crypto/_aes/ct64/api.odin @@ -0,0 +1,86 @@ +package aes_ct64 + +import "base:intrinsics" +import "core:mem" + +STRIDE :: 4 + +// Context is a keyed AES (ECB) instance. +Context :: struct { + _sk_exp: [120]u64, + _num_rounds: int, +} + +// init initializes a context for AES with the provided key. +init :: proc(ctx: ^Context, key: []byte) { + skey: [30]u64 = --- + + ctx._num_rounds = keysched(skey[:], key) + skey_expand(ctx._sk_exp[:], skey[:], ctx._num_rounds) +} + +// encrypt_block sets `dst` to `AES-ECB-Encrypt(src)`. +encrypt_block :: proc(ctx: ^Context, dst, src: []byte) { + q: [8]u64 + load_blockx1(&q, src) + _encrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blockx1(dst, &q) +} + +// encrypt_block sets `dst` to `AES-ECB-Decrypt(src)`. +decrypt_block :: proc(ctx: ^Context, dst, src: []byte) { + q: [8]u64 + load_blockx1(&q, src) + _decrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blockx1(dst, &q) +} + +// encrypt_blocks sets `dst` to `AES-ECB-Encrypt(src[0], .. src[n])`. +encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) { + q: [8]u64 = --- + src, dst := src, dst + + n := len(src) + for n > 4 { + load_blocks(&q, src[0:4]) + _encrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blocks(dst[0:4], &q) + + src = src[4:] + dst = dst[4:] + n -= 4 + } + if n > 0 { + load_blocks(&q, src) + _encrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blocks(dst, &q) + } +} + +// decrypt_blocks sets dst to `AES-ECB-Decrypt(src[0], .. src[n])`. +decrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) { + q: [8]u64 = --- + src, dst := src, dst + + n := len(src) + for n > 4 { + load_blocks(&q, src[0:4]) + _decrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blocks(dst[0:4], &q) + + src = src[4:] + dst = dst[4:] + n -= 4 + } + if n > 0 { + load_blocks(&q, src) + _decrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blocks(dst, &q) + } +} + +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + mem.zero_explicit(ctx, size_of(ctx)) +} diff --git a/core/crypto/_aes/ct64/ct64.odin b/core/crypto/_aes/ct64/ct64.odin new file mode 100644 index 000000000..f198cab81 --- /dev/null +++ b/core/crypto/_aes/ct64/ct64.odin @@ -0,0 +1,265 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" + +// Bitsliced AES for 64-bit general purpose (integer) registers. Each +// invocation will process up to 4 blocks at a time. This implementation +// is derived from the BearSSL ct64 code, and distributed under a 1-clause +// BSD license with permission from the original author. +// +// WARNING: "hic sunt dracones" +// +// This package also deliberately exposes enough internals to be able to +// function as a replacement for `AESENC` and `AESDEC` from AES-NI, to +// allow the implementation of non-AES primitives that use the AES round +// function such as AEGIS and Deoxys-II. This should ONLY be done when +// implementing something other than AES itself. + +sub_bytes :: proc "contextless" (q: ^[8]u64) { + // This S-box implementation is a straightforward translation of + // the circuit described by Boyar and Peralta in "A new + // combinational logic minimization technique with applications + // to cryptology" (https://eprint.iacr.org/2009/191.pdf). + // + // Note that variables x* (input) and s* (output) are numbered + // in "reverse" order (x0 is the high bit, x7 is the low bit). + + x0 := q[7] + x1 := q[6] + x2 := q[5] + x3 := q[4] + x4 := q[3] + x5 := q[2] + x6 := q[1] + x7 := q[0] + + // Top linear transformation. + y14 := x3 ~ x5 + y13 := x0 ~ x6 + y9 := x0 ~ x3 + y8 := x0 ~ x5 + t0 := x1 ~ x2 + y1 := t0 ~ x7 + y4 := y1 ~ x3 + y12 := y13 ~ y14 + y2 := y1 ~ x0 + y5 := y1 ~ x6 + y3 := y5 ~ y8 + t1 := x4 ~ y12 + y15 := t1 ~ x5 + y20 := t1 ~ x1 + y6 := y15 ~ x7 + y10 := y15 ~ t0 + y11 := y20 ~ y9 + y7 := x7 ~ y11 + y17 := y10 ~ y11 + y19 := y10 ~ y8 + y16 := t0 ~ y11 + y21 := y13 ~ y16 + y18 := x0 ~ y16 + + // Non-linear section. + t2 := y12 & y15 + t3 := y3 & y6 + t4 := t3 ~ t2 + t5 := y4 & x7 + t6 := t5 ~ t2 + t7 := y13 & y16 + t8 := y5 & y1 + t9 := t8 ~ t7 + t10 := y2 & y7 + t11 := t10 ~ t7 + t12 := y9 & y11 + t13 := y14 & y17 + t14 := t13 ~ t12 + t15 := y8 & y10 + t16 := t15 ~ t12 + t17 := t4 ~ t14 + t18 := t6 ~ t16 + t19 := t9 ~ t14 + t20 := t11 ~ t16 + t21 := t17 ~ y20 + t22 := t18 ~ y19 + t23 := t19 ~ y21 + t24 := t20 ~ y18 + + t25 := t21 ~ t22 + t26 := t21 & t23 + t27 := t24 ~ t26 + t28 := t25 & t27 + t29 := t28 ~ t22 + t30 := t23 ~ t24 + t31 := t22 ~ t26 + t32 := t31 & t30 + t33 := t32 ~ t24 + t34 := t23 ~ t33 + t35 := t27 ~ t33 + t36 := t24 & t35 + t37 := t36 ~ t34 + t38 := t27 ~ t36 + t39 := t29 & t38 + t40 := t25 ~ t39 + + t41 := t40 ~ t37 + t42 := t29 ~ t33 + t43 := t29 ~ t40 + t44 := t33 ~ t37 + t45 := t42 ~ t41 + z0 := t44 & y15 + z1 := t37 & y6 + z2 := t33 & x7 + z3 := t43 & y16 + z4 := t40 & y1 + z5 := t29 & y7 + z6 := t42 & y11 + z7 := t45 & y17 + z8 := t41 & y10 + z9 := t44 & y12 + z10 := t37 & y3 + z11 := t33 & y4 + z12 := t43 & y13 + z13 := t40 & y5 + z14 := t29 & y2 + z15 := t42 & y9 + z16 := t45 & y14 + z17 := t41 & y8 + + // Bottom linear transformation. + t46 := z15 ~ z16 + t47 := z10 ~ z11 + t48 := z5 ~ z13 + t49 := z9 ~ z10 + t50 := z2 ~ z12 + t51 := z2 ~ z5 + t52 := z7 ~ z8 + t53 := z0 ~ z3 + t54 := z6 ~ z7 + t55 := z16 ~ z17 + t56 := z12 ~ t48 + t57 := t50 ~ t53 + t58 := z4 ~ t46 + t59 := z3 ~ t54 + t60 := t46 ~ t57 + t61 := z14 ~ t57 + t62 := t52 ~ t58 + t63 := t49 ~ t58 + t64 := z4 ~ t59 + t65 := t61 ~ t62 + t66 := z1 ~ t63 + s0 := t59 ~ t63 + s6 := t56 ~ ~t62 + s7 := t48 ~ ~t60 + t67 := t64 ~ t65 + s3 := t53 ~ t66 + s4 := t51 ~ t66 + s5 := t47 ~ t65 + s1 := t64 ~ ~s3 + s2 := t55 ~ ~t67 + + q[7] = s0 + q[6] = s1 + q[5] = s2 + q[4] = s3 + q[3] = s4 + q[2] = s5 + q[1] = s6 + q[0] = s7 +} + +orthogonalize :: proc "contextless" (q: ^[8]u64) { + CL2 :: 0x5555555555555555 + CH2 :: 0xAAAAAAAAAAAAAAAA + q[0], q[1] = (q[0] & CL2) | ((q[1] & CL2) << 1), ((q[0] & CH2) >> 1) | (q[1] & CH2) + q[2], q[3] = (q[2] & CL2) | ((q[3] & CL2) << 1), ((q[2] & CH2) >> 1) | (q[3] & CH2) + q[4], q[5] = (q[4] & CL2) | ((q[5] & CL2) << 1), ((q[4] & CH2) >> 1) | (q[5] & CH2) + q[6], q[7] = (q[6] & CL2) | ((q[7] & CL2) << 1), ((q[6] & CH2) >> 1) | (q[7] & CH2) + + CL4 :: 0x3333333333333333 + CH4 :: 0xCCCCCCCCCCCCCCCC + q[0], q[2] = (q[0] & CL4) | ((q[2] & CL4) << 2), ((q[0] & CH4) >> 2) | (q[2] & CH4) + q[1], q[3] = (q[1] & CL4) | ((q[3] & CL4) << 2), ((q[1] & CH4) >> 2) | (q[3] & CH4) + q[4], q[6] = (q[4] & CL4) | ((q[6] & CL4) << 2), ((q[4] & CH4) >> 2) | (q[6] & CH4) + q[5], q[7] = (q[5] & CL4) | ((q[7] & CL4) << 2), ((q[5] & CH4) >> 2) | (q[7] & CH4) + + CL8 :: 0x0F0F0F0F0F0F0F0F + CH8 :: 0xF0F0F0F0F0F0F0F0 + q[0], q[4] = (q[0] & CL8) | ((q[4] & CL8) << 4), ((q[0] & CH8) >> 4) | (q[4] & CH8) + q[1], q[5] = (q[1] & CL8) | ((q[5] & CL8) << 4), ((q[1] & CH8) >> 4) | (q[5] & CH8) + q[2], q[6] = (q[2] & CL8) | ((q[6] & CL8) << 4), ((q[2] & CH8) >> 4) | (q[6] & CH8) + q[3], q[7] = (q[3] & CL8) | ((q[7] & CL8) << 4), ((q[3] & CH8) >> 4) | (q[7] & CH8) +} + +@(require_results) +interleave_in :: proc "contextless" (w: []u32) -> (q0, q1: u64) #no_bounds_check { + if len(w) < 4 { + intrinsics.trap() + } + x0, x1, x2, x3 := u64(w[0]), u64(w[1]), u64(w[2]), u64(w[3]) + x0 |= (x0 << 16) + x1 |= (x1 << 16) + x2 |= (x2 << 16) + x3 |= (x3 << 16) + x0 &= 0x0000FFFF0000FFFF + x1 &= 0x0000FFFF0000FFFF + x2 &= 0x0000FFFF0000FFFF + x3 &= 0x0000FFFF0000FFFF + x0 |= (x0 << 8) + x1 |= (x1 << 8) + x2 |= (x2 << 8) + x3 |= (x3 << 8) + x0 &= 0x00FF00FF00FF00FF + x1 &= 0x00FF00FF00FF00FF + x2 &= 0x00FF00FF00FF00FF + x3 &= 0x00FF00FF00FF00FF + q0 = x0 | (x2 << 8) + q1 = x1 | (x3 << 8) + return +} + +@(require_results) +interleave_out :: proc "contextless" (q0, q1: u64) -> (w0, w1, w2, w3: u32) { + x0 := q0 & 0x00FF00FF00FF00FF + x1 := q1 & 0x00FF00FF00FF00FF + x2 := (q0 >> 8) & 0x00FF00FF00FF00FF + x3 := (q1 >> 8) & 0x00FF00FF00FF00FF + x0 |= (x0 >> 8) + x1 |= (x1 >> 8) + x2 |= (x2 >> 8) + x3 |= (x3 >> 8) + x0 &= 0x0000FFFF0000FFFF + x1 &= 0x0000FFFF0000FFFF + x2 &= 0x0000FFFF0000FFFF + x3 &= 0x0000FFFF0000FFFF + w0 = u32(x0) | u32(x0 >> 16) + w1 = u32(x1) | u32(x1 >> 16) + w2 = u32(x2) | u32(x2 >> 16) + w3 = u32(x3) | u32(x3 >> 16) + return +} + +@(private) +rotr32 :: #force_inline proc "contextless" (x: u64) -> u64 { + return (x << 32) | (x >> 32) +} diff --git a/core/crypto/_aes/ct64/ct64_dec.odin b/core/crypto/_aes/ct64/ct64_dec.odin new file mode 100644 index 000000000..408ee6002 --- /dev/null +++ b/core/crypto/_aes/ct64/ct64_dec.odin @@ -0,0 +1,135 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" + +inv_sub_bytes :: proc "contextless" (q: ^[8]u64) { + // AES S-box is: + // S(x) = A(I(x)) ^ 0x63 + // where I() is inversion in GF(256), and A() is a linear + // transform (0 is formally defined to be its own inverse). + // Since inversion is an involution, the inverse S-box can be + // computed from the S-box as: + // iS(x) = B(S(B(x ^ 0x63)) ^ 0x63) + // where B() is the inverse of A(). Indeed, for any y in GF(256): + // iS(S(y)) = B(A(I(B(A(I(y)) ^ 0x63 ^ 0x63))) ^ 0x63 ^ 0x63) = y + // + // Note: we reuse the implementation of the forward S-box, + // instead of duplicating it here, so that total code size is + // lower. By merging the B() transforms into the S-box circuit + // we could make faster CBC decryption, but CBC decryption is + // already quite faster than CBC encryption because we can + // process four blocks in parallel. + + q0 := ~q[0] + q1 := ~q[1] + q2 := q[2] + q3 := q[3] + q4 := q[4] + q5 := ~q[5] + q6 := ~q[6] + q7 := q[7] + q[7] = q1 ~ q4 ~ q6 + q[6] = q0 ~ q3 ~ q5 + q[5] = q7 ~ q2 ~ q4 + q[4] = q6 ~ q1 ~ q3 + q[3] = q5 ~ q0 ~ q2 + q[2] = q4 ~ q7 ~ q1 + q[1] = q3 ~ q6 ~ q0 + q[0] = q2 ~ q5 ~ q7 + + sub_bytes(q) + + q0 = ~q[0] + q1 = ~q[1] + q2 = q[2] + q3 = q[3] + q4 = q[4] + q5 = ~q[5] + q6 = ~q[6] + q7 = q[7] + q[7] = q1 ~ q4 ~ q6 + q[6] = q0 ~ q3 ~ q5 + q[5] = q7 ~ q2 ~ q4 + q[4] = q6 ~ q1 ~ q3 + q[3] = q5 ~ q0 ~ q2 + q[2] = q4 ~ q7 ~ q1 + q[1] = q3 ~ q6 ~ q0 + q[0] = q2 ~ q5 ~ q7 +} + +inv_shift_rows :: proc "contextless" (q: ^[8]u64) { + for x, i in q { + q[i] = + (x & 0x000000000000FFFF) | + ((x & 0x000000000FFF0000) << 4) | + ((x & 0x00000000F0000000) >> 12) | + ((x & 0x000000FF00000000) << 8) | + ((x & 0x0000FF0000000000) >> 8) | + ((x & 0x000F000000000000) << 12) | + ((x & 0xFFF0000000000000) >> 4) + } +} + +inv_mix_columns :: proc "contextless" (q: ^[8]u64) { + q0 := q[0] + q1 := q[1] + q2 := q[2] + q3 := q[3] + q4 := q[4] + q5 := q[5] + q6 := q[6] + q7 := q[7] + r0 := (q0 >> 16) | (q0 << 48) + r1 := (q1 >> 16) | (q1 << 48) + r2 := (q2 >> 16) | (q2 << 48) + r3 := (q3 >> 16) | (q3 << 48) + r4 := (q4 >> 16) | (q4 << 48) + r5 := (q5 >> 16) | (q5 << 48) + r6 := (q6 >> 16) | (q6 << 48) + r7 := (q7 >> 16) | (q7 << 48) + + q[0] = q5 ~ q6 ~ q7 ~ r0 ~ r5 ~ r7 ~ rotr32(q0 ~ q5 ~ q6 ~ r0 ~ r5) + q[1] = q0 ~ q5 ~ r0 ~ r1 ~ r5 ~ r6 ~ r7 ~ rotr32(q1 ~ q5 ~ q7 ~ r1 ~ r5 ~ r6) + q[2] = q0 ~ q1 ~ q6 ~ r1 ~ r2 ~ r6 ~ r7 ~ rotr32(q0 ~ q2 ~ q6 ~ r2 ~ r6 ~ r7) + q[3] = q0 ~ q1 ~ q2 ~ q5 ~ q6 ~ r0 ~ r2 ~ r3 ~ r5 ~ rotr32(q0 ~ q1 ~ q3 ~ q5 ~ q6 ~ q7 ~ r0 ~ r3 ~ r5 ~ r7) + q[4] = q1 ~ q2 ~ q3 ~ q5 ~ r1 ~ r3 ~ r4 ~ r5 ~ r6 ~ r7 ~ rotr32(q1 ~ q2 ~ q4 ~ q5 ~ q7 ~ r1 ~ r4 ~ r5 ~ r6) + q[5] = q2 ~ q3 ~ q4 ~ q6 ~ r2 ~ r4 ~ r5 ~ r6 ~ r7 ~ rotr32(q2 ~ q3 ~ q5 ~ q6 ~ r2 ~ r5 ~ r6 ~ r7) + q[6] = q3 ~ q4 ~ q5 ~ q7 ~ r3 ~ r5 ~ r6 ~ r7 ~ rotr32(q3 ~ q4 ~ q6 ~ q7 ~ r3 ~ r6 ~ r7) + q[7] = q4 ~ q5 ~ q6 ~ r4 ~ r6 ~ r7 ~ rotr32(q4 ~ q5 ~ q7 ~ r4 ~ r7) +} + +@(private) +_decrypt :: proc "contextless" (q: ^[8]u64, skey: []u64, num_rounds: int) { + add_round_key(q, skey[num_rounds << 3:]) + for u := num_rounds - 1; u > 0; u -= 1 { + inv_shift_rows(q) + inv_sub_bytes(q) + add_round_key(q, skey[u << 3:]) + inv_mix_columns(q) + } + inv_shift_rows(q) + inv_sub_bytes(q) + add_round_key(q, skey) +} diff --git a/core/crypto/_aes/ct64/ct64_enc.odin b/core/crypto/_aes/ct64/ct64_enc.odin new file mode 100644 index 000000000..36d4aebc8 --- /dev/null +++ b/core/crypto/_aes/ct64/ct64_enc.odin @@ -0,0 +1,95 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" + +add_round_key :: proc "contextless" (q: ^[8]u64, sk: []u64) #no_bounds_check { + if len(sk) < 8 { + intrinsics.trap() + } + + q[0] ~= sk[0] + q[1] ~= sk[1] + q[2] ~= sk[2] + q[3] ~= sk[3] + q[4] ~= sk[4] + q[5] ~= sk[5] + q[6] ~= sk[6] + q[7] ~= sk[7] +} + +shift_rows :: proc "contextless" (q: ^[8]u64) { + for x, i in q { + q[i] = + (x & 0x000000000000FFFF) | + ((x & 0x00000000FFF00000) >> 4) | + ((x & 0x00000000000F0000) << 12) | + ((x & 0x0000FF0000000000) >> 8) | + ((x & 0x000000FF00000000) << 8) | + ((x & 0xF000000000000000) >> 12) | + ((x & 0x0FFF000000000000) << 4) + } +} + +mix_columns :: proc "contextless" (q: ^[8]u64) { + q0 := q[0] + q1 := q[1] + q2 := q[2] + q3 := q[3] + q4 := q[4] + q5 := q[5] + q6 := q[6] + q7 := q[7] + r0 := (q0 >> 16) | (q0 << 48) + r1 := (q1 >> 16) | (q1 << 48) + r2 := (q2 >> 16) | (q2 << 48) + r3 := (q3 >> 16) | (q3 << 48) + r4 := (q4 >> 16) | (q4 << 48) + r5 := (q5 >> 16) | (q5 << 48) + r6 := (q6 >> 16) | (q6 << 48) + r7 := (q7 >> 16) | (q7 << 48) + + q[0] = q7 ~ r7 ~ r0 ~ rotr32(q0 ~ r0) + q[1] = q0 ~ r0 ~ q7 ~ r7 ~ r1 ~ rotr32(q1 ~ r1) + q[2] = q1 ~ r1 ~ r2 ~ rotr32(q2 ~ r2) + q[3] = q2 ~ r2 ~ q7 ~ r7 ~ r3 ~ rotr32(q3 ~ r3) + q[4] = q3 ~ r3 ~ q7 ~ r7 ~ r4 ~ rotr32(q4 ~ r4) + q[5] = q4 ~ r4 ~ r5 ~ rotr32(q5 ~ r5) + q[6] = q5 ~ r5 ~ r6 ~ rotr32(q6 ~ r6) + q[7] = q6 ~ r6 ~ r7 ~ rotr32(q7 ~ r7) +} + +@(private) +_encrypt :: proc "contextless" (q: ^[8]u64, skey: []u64, num_rounds: int) { + add_round_key(q, skey) + for u in 1 ..< num_rounds { + sub_bytes(q) + shift_rows(q) + mix_columns(q) + add_round_key(q, skey[u << 3:]) + } + sub_bytes(q) + shift_rows(q) + add_round_key(q, skey[num_rounds << 3:]) +} diff --git a/core/crypto/_aes/ct64/ct64_keysched.odin b/core/crypto/_aes/ct64/ct64_keysched.odin new file mode 100644 index 000000000..060a2c03e --- /dev/null +++ b/core/crypto/_aes/ct64/ct64_keysched.odin @@ -0,0 +1,179 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:encoding/endian" +import "core:mem" + +@(private, require_results) +sub_word :: proc "contextless" (x: u32) -> u32 { + q := [8]u64{u64(x), 0, 0, 0, 0, 0, 0, 0} + + orthogonalize(&q) + sub_bytes(&q) + orthogonalize(&q) + ret := u32(q[0]) + + mem.zero_explicit(&q[0], size_of(u64)) + + return ret +} + +@(private, require_results) +keysched :: proc(comp_skey: []u64, key: []byte) -> int { + num_rounds, key_len := 0, len(key) + switch key_len { + case _aes.KEY_SIZE_128: + num_rounds = _aes.ROUNDS_128 + case _aes.KEY_SIZE_192: + num_rounds = _aes.ROUNDS_192 + case _aes.KEY_SIZE_256: + num_rounds = _aes.ROUNDS_256 + case: + panic("crypto/aes: invalid AES key size") + } + + skey: [60]u32 = --- + nk, nkf := key_len >> 2, (num_rounds + 1) << 2 + for i in 0 ..< nk { + skey[i] = endian.unchecked_get_u32le(key[i << 2:]) + } + tmp := skey[(key_len >> 2) - 1] + for i, j, k := nk, 0, 0; i < nkf; i += 1 { + if j == 0 { + tmp = (tmp << 24) | (tmp >> 8) + tmp = sub_word(tmp) ~ u32(_aes.RCON[k]) + } else if nk > 6 && j == 4 { + tmp = sub_word(tmp) + } + tmp ~= skey[i - nk] + skey[i] = tmp + if j += 1; j == nk { + j = 0 + k += 1 + } + } + + q: [8]u64 = --- + for i, j := 0, 0; i < nkf; i, j = i + 4, j + 2 { + q[0], q[4] = interleave_in(skey[i:]) + q[1] = q[0] + q[2] = q[0] + q[3] = q[0] + q[5] = q[4] + q[6] = q[4] + q[7] = q[4] + orthogonalize(&q) + comp_skey[j + 0] = + (q[0] & 0x1111111111111111) | + (q[1] & 0x2222222222222222) | + (q[2] & 0x4444444444444444) | + (q[3] & 0x8888888888888888) + comp_skey[j + 1] = + (q[4] & 0x1111111111111111) | + (q[5] & 0x2222222222222222) | + (q[6] & 0x4444444444444444) | + (q[7] & 0x8888888888888888) + } + + mem.zero_explicit(&skey, size_of(skey)) + mem.zero_explicit(&q, size_of(q)) + + return num_rounds +} + +@(private) +skey_expand :: proc "contextless" (skey, comp_skey: []u64, num_rounds: int) { + n := (num_rounds + 1) << 1 + for u, v := 0, 0; u < n; u, v = u + 1, v + 4 { + x0 := comp_skey[u] + x1, x2, x3 := x0, x0, x0 + x0 &= 0x1111111111111111 + x1 &= 0x2222222222222222 + x2 &= 0x4444444444444444 + x3 &= 0x8888888888888888 + x1 >>= 1 + x2 >>= 2 + x3 >>= 3 + skey[v + 0] = (x0 << 4) - x0 + skey[v + 1] = (x1 << 4) - x1 + skey[v + 2] = (x2 << 4) - x2 + skey[v + 3] = (x3 << 4) - x3 + } +} + +orthogonalize_roundkey :: proc "contextless" (qq: []u64, key: []byte) { + if len(qq) < 8 || len(key) != 16 { + intrinsics.trap() + } + + skey: [4]u32 = --- + skey[0] = endian.unchecked_get_u32le(key[0:]) + skey[1] = endian.unchecked_get_u32le(key[4:]) + skey[2] = endian.unchecked_get_u32le(key[8:]) + skey[3] = endian.unchecked_get_u32le(key[12:]) + + q: [8]u64 = --- + q[0], q[4] = interleave_in(skey[:]) + q[1] = q[0] + q[2] = q[0] + q[3] = q[0] + q[5] = q[4] + q[6] = q[4] + q[7] = q[4] + orthogonalize(&q) + + comp_skey: [2]u64 = --- + comp_skey[0] = + (q[0] & 0x1111111111111111) | + (q[1] & 0x2222222222222222) | + (q[2] & 0x4444444444444444) | + (q[3] & 0x8888888888888888) + comp_skey[1] = + (q[4] & 0x1111111111111111) | + (q[5] & 0x2222222222222222) | + (q[6] & 0x4444444444444444) | + (q[7] & 0x8888888888888888) + + for x, u in comp_skey { + x0 := x + x1, x2, x3 := x0, x0, x0 + x0 &= 0x1111111111111111 + x1 &= 0x2222222222222222 + x2 &= 0x4444444444444444 + x3 &= 0x8888888888888888 + x1 >>= 1 + x2 >>= 2 + x3 >>= 3 + qq[u * 4 + 0] = (x0 << 4) - x0 + qq[u * 4 + 1] = (x1 << 4) - x1 + qq[u * 4 + 2] = (x2 << 4) - x2 + qq[u * 4 + 3] = (x3 << 4) - x3 + } + + mem.zero_explicit(&skey, size_of(skey)) + mem.zero_explicit(&q, size_of(q)) + mem.zero_explicit(&comp_skey, size_of(comp_skey)) +} diff --git a/core/crypto/_aes/ct64/ghash.odin b/core/crypto/_aes/ct64/ghash.odin new file mode 100644 index 000000000..21ac2ca97 --- /dev/null +++ b/core/crypto/_aes/ct64/ghash.odin @@ -0,0 +1,136 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:encoding/endian" + +@(private = "file") +bmul64 :: proc "contextless" (x, y: u64) -> u64 { + x0 := x & 0x1111111111111111 + x1 := x & 0x2222222222222222 + x2 := x & 0x4444444444444444 + x3 := x & 0x8888888888888888 + y0 := y & 0x1111111111111111 + y1 := y & 0x2222222222222222 + y2 := y & 0x4444444444444444 + y3 := y & 0x8888888888888888 + z0 := (x0 * y0) ~ (x1 * y3) ~ (x2 * y2) ~ (x3 * y1) + z1 := (x0 * y1) ~ (x1 * y0) ~ (x2 * y3) ~ (x3 * y2) + z2 := (x0 * y2) ~ (x1 * y1) ~ (x2 * y0) ~ (x3 * y3) + z3 := (x0 * y3) ~ (x1 * y2) ~ (x2 * y1) ~ (x3 * y0) + z0 &= 0x1111111111111111 + z1 &= 0x2222222222222222 + z2 &= 0x4444444444444444 + z3 &= 0x8888888888888888 + return z0 | z1 | z2 | z3 +} + +@(private = "file") +rev64 :: proc "contextless" (x: u64) -> u64 { + x := x + x = ((x & 0x5555555555555555) << 1) | ((x >> 1) & 0x5555555555555555) + x = ((x & 0x3333333333333333) << 2) | ((x >> 2) & 0x3333333333333333) + x = ((x & 0x0F0F0F0F0F0F0F0F) << 4) | ((x >> 4) & 0x0F0F0F0F0F0F0F0F) + x = ((x & 0x00FF00FF00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF00FF00FF) + x = ((x & 0x0000FFFF0000FFFF) << 16) | ((x >> 16) & 0x0000FFFF0000FFFF) + return (x << 32) | (x >> 32) +} + +// ghash calculates the GHASH of data, with the key `key`, and input `dst` +// and `data`, and stores the resulting digest in `dst`. +// +// Note: `dst` is both an input and an output, to support easy implementation +// of GCM. +ghash :: proc "contextless" (dst, key, data: []byte) { + if len(dst) != _aes.GHASH_BLOCK_SIZE || len(key) != _aes.GHASH_BLOCK_SIZE { + intrinsics.trap() + } + + buf := data + l := len(buf) + + y1 := endian.unchecked_get_u64be(dst[0:]) + y0 := endian.unchecked_get_u64be(dst[8:]) + h1 := endian.unchecked_get_u64be(key[0:]) + h0 := endian.unchecked_get_u64be(key[8:]) + h0r := rev64(h0) + h1r := rev64(h1) + h2 := h0 ~ h1 + h2r := h0r ~ h1r + + src: []byte + for l > 0 { + if l >= _aes.GHASH_BLOCK_SIZE { + src = buf + buf = buf[_aes.GHASH_BLOCK_SIZE:] + l -= _aes.GHASH_BLOCK_SIZE + } else { + tmp: [_aes.GHASH_BLOCK_SIZE]byte + copy(tmp[:], buf) + src = tmp[:] + l = 0 + } + y1 ~= endian.unchecked_get_u64be(src) + y0 ~= endian.unchecked_get_u64be(src[8:]) + + y0r := rev64(y0) + y1r := rev64(y1) + y2 := y0 ~ y1 + y2r := y0r ~ y1r + + z0 := bmul64(y0, h0) + z1 := bmul64(y1, h1) + z2 := bmul64(y2, h2) + z0h := bmul64(y0r, h0r) + z1h := bmul64(y1r, h1r) + z2h := bmul64(y2r, h2r) + z2 ~= z0 ~ z1 + z2h ~= z0h ~ z1h + z0h = rev64(z0h) >> 1 + z1h = rev64(z1h) >> 1 + z2h = rev64(z2h) >> 1 + + v0 := z0 + v1 := z0h ~ z2 + v2 := z1 ~ z2h + v3 := z1h + + v3 = (v3 << 1) | (v2 >> 63) + v2 = (v2 << 1) | (v1 >> 63) + v1 = (v1 << 1) | (v0 >> 63) + v0 = (v0 << 1) + + v2 ~= v0 ~ (v0 >> 1) ~ (v0 >> 2) ~ (v0 >> 7) + v1 ~= (v0 << 63) ~ (v0 << 62) ~ (v0 << 57) + v3 ~= v1 ~ (v1 >> 1) ~ (v1 >> 2) ~ (v1 >> 7) + v2 ~= (v1 << 63) ~ (v1 << 62) ~ (v1 << 57) + + y0 = v2 + y1 = v3 + } + + endian.unchecked_put_u64be(dst[0:], y1) + endian.unchecked_put_u64be(dst[8:], y0) +} diff --git a/core/crypto/_aes/ct64/helpers.odin b/core/crypto/_aes/ct64/helpers.odin new file mode 100644 index 000000000..169271f6d --- /dev/null +++ b/core/crypto/_aes/ct64/helpers.odin @@ -0,0 +1,75 @@ +package aes_ct64 + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:encoding/endian" + +load_blockx1 :: proc "contextless" (q: ^[8]u64, src: []byte) { + if len(src) != _aes.BLOCK_SIZE { + intrinsics.trap() + } + + w: [4]u32 = --- + w[0] = endian.unchecked_get_u32le(src[0:]) + w[1] = endian.unchecked_get_u32le(src[4:]) + w[2] = endian.unchecked_get_u32le(src[8:]) + w[3] = endian.unchecked_get_u32le(src[12:]) + q[0], q[4] = interleave_in(w[:]) + orthogonalize(q) +} + +store_blockx1 :: proc "contextless" (dst: []byte, q: ^[8]u64) { + if len(dst) != _aes.BLOCK_SIZE { + intrinsics.trap() + } + + orthogonalize(q) + w0, w1, w2, w3 := interleave_out(q[0], q[4]) + endian.unchecked_put_u32le(dst[0:], w0) + endian.unchecked_put_u32le(dst[4:], w1) + endian.unchecked_put_u32le(dst[8:], w2) + endian.unchecked_put_u32le(dst[12:], w3) +} + +load_blocks :: proc "contextless" (q: ^[8]u64, src: [][]byte) { + if n := len(src); n > STRIDE || n == 0 { + intrinsics.trap() + } + + w: [4]u32 = --- + for s, i in src { + if len(s) != _aes.BLOCK_SIZE { + intrinsics.trap() + } + + w[0] = endian.unchecked_get_u32le(s[0:]) + w[1] = endian.unchecked_get_u32le(s[4:]) + w[2] = endian.unchecked_get_u32le(s[8:]) + w[3] = endian.unchecked_get_u32le(s[12:]) + q[i], q[i + 4] = interleave_in(w[:]) + } + orthogonalize(q) +} + +store_blocks :: proc "contextless" (dst: [][]byte, q: ^[8]u64) { + if n := len(dst); n > STRIDE || n == 0 { + intrinsics.trap() + } + + orthogonalize(q) + for d, i in dst { + // Allow storing [0,4] blocks. + if d == nil { + break + } + if len(d) != _aes.BLOCK_SIZE { + intrinsics.trap() + } + + w0, w1, w2, w3 := interleave_out(q[i], q[i + 4]) + endian.unchecked_put_u32le(d[0:], w0) + endian.unchecked_put_u32le(d[4:], w1) + endian.unchecked_put_u32le(d[8:], w2) + endian.unchecked_put_u32le(d[12:], w3) + } +} diff --git a/core/crypto/_aes/hw_intel/api.odin b/core/crypto/_aes/hw_intel/api.odin new file mode 100644 index 000000000..5cb5a68bb --- /dev/null +++ b/core/crypto/_aes/hw_intel/api.odin @@ -0,0 +1,43 @@ +//+build amd64 +package aes_hw_intel + +import "core:sys/info" + +// is_supporte returns true iff hardware accelerated AES +// is supported. +is_supported :: proc "contextless" () -> bool { + features, ok := info.cpu_features.? + if !ok { + return false + } + + // Note: Everything with AES-NI and PCLMULQDQ has support for + // the required SSE extxtensions. + req_features :: info.CPU_Features{ + .sse2, + .ssse3, + .sse41, + .aes, + .pclmulqdq, + } + return features >= req_features +} + +// Context is a keyed AES (ECB) instance. +Context :: struct { + // Note: The ideal thing to do is for the expanded round keys to be + // arrays of `__m128i`, however that implies alignment (or using AVX). + // + // All the people using e-waste processors that don't support an + // insturction set that has been around for over 10 years are why + // we can't have nice things. + _sk_exp_enc: [15][16]byte, + _sk_exp_dec: [15][16]byte, + _num_rounds: int, +} + +// init initializes a context for AES with the provided key. +init :: proc(ctx: ^Context, key: []byte) { + keysched(ctx, key) +} + diff --git a/core/crypto/_aes/hw_intel/ghash.odin b/core/crypto/_aes/hw_intel/ghash.odin new file mode 100644 index 000000000..9a5208523 --- /dev/null +++ b/core/crypto/_aes/hw_intel/ghash.odin @@ -0,0 +1,281 @@ +// Copyright (c) 2017 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//+build amd64 +package aes_hw_intel + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:simd" +import "core:simd/x86" + +@(private = "file") +GHASH_STRIDE_HW :: 4 +@(private = "file") +GHASH_STRIDE_BYTES_HW :: GHASH_STRIDE_HW * _aes.GHASH_BLOCK_SIZE + +// GHASH is defined over elements of GF(2^128) with "full little-endian" +// representation: leftmost byte is least significant, and, within each +// byte, leftmost _bit_ is least significant. The natural ordering in +// x86 is "mixed little-endian": bytes are ordered from least to most +// significant, but bits within a byte are in most-to-least significant +// order. Going to full little-endian representation would require +// reversing bits within each byte, which is doable but expensive. +// +// Instead, we go to full big-endian representation, by swapping bytes +// around, which is done with a single _mm_shuffle_epi8() opcode (it +// comes with SSSE3; all CPU that offer pclmulqdq also have SSSE3). We +// can use a full big-endian representation because in a carryless +// multiplication, we have a nice bit reversal property: +// +// rev_128(x) * rev_128(y) = rev_255(x * y) +// +// So by using full big-endian, we still get the right result, except +// that it is right-shifted by 1 bit. The left-shift is relatively +// inexpensive, and it can be mutualised. +// +// Since SSE2 opcodes do not have facilities for shitfting full 128-bit +// values with bit precision, we have to break down values into 64-bit +// chunks. We number chunks from 0 to 3 in left to right order. + +@(private = "file") +byteswap_index := transmute(x86.__m128i)simd.i8x16{ + // Note: simd.i8x16 is reverse order from x86._mm_set_epi8. + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, +} + +@(private = "file", require_results, enable_target_feature = "sse2,ssse3") +byteswap :: #force_inline proc "contextless" (x: x86.__m128i) -> x86.__m128i { + return x86._mm_shuffle_epi8(x, byteswap_index) +} + +// From a 128-bit value kw, compute kx as the XOR of the two 64-bit +// halves of kw (into the right half of kx; left half is unspecified), +// and return kx. +@(private = "file", require_results, enable_target_feature = "sse2") +bk :: #force_inline proc "contextless" (kw: x86.__m128i) -> x86.__m128i { + return x86._mm_xor_si128(kw, x86._mm_shuffle_epi32(kw, 0x0e)) +} + +// Combine two 64-bit values (k0:k1) into a 128-bit (kw) value and +// the XOR of the two values (kx), and return (kw, kx). +@(private = "file", enable_target_feature = "sse2") +pbk :: #force_inline proc "contextless" (k0, k1: x86.__m128i) -> (x86.__m128i, x86.__m128i) { + kw := x86._mm_unpacklo_epi64(k1, k0) + kx := x86._mm_xor_si128(k0, k1) + return kw, kx +} + +// Left-shift by 1 bit a 256-bit value (in four 64-bit words). +@(private = "file", require_results, enable_target_feature = "sse2") +sl_256 :: #force_inline proc "contextless" (x0, x1, x2, x3: x86.__m128i) -> (x86.__m128i, x86.__m128i, x86.__m128i, x86.__m128i) { + x0, x1, x2, x3 := x0, x1, x2, x3 + + x0 = x86._mm_or_si128(x86._mm_slli_epi64(x0, 1), x86._mm_srli_epi64(x1, 63)) + x1 = x86._mm_or_si128(x86._mm_slli_epi64(x1, 1), x86._mm_srli_epi64(x2, 63)) + x2 = x86._mm_or_si128(x86._mm_slli_epi64(x2, 1), x86._mm_srli_epi64(x3, 63)) + x3 = x86._mm_slli_epi64(x3, 1) + + return x0, x1, x2, x3 +} + +// Perform reduction in GF(2^128). +@(private = "file", require_results, enable_target_feature = "sse2") +reduce_f128 :: #force_inline proc "contextless" (x0, x1, x2, x3: x86.__m128i) -> (x86.__m128i, x86.__m128i) { + x0, x1, x2 := x0, x1, x2 + + x1 = x86._mm_xor_si128( + x1, + x86._mm_xor_si128( + x86._mm_xor_si128( + x3, + x86._mm_srli_epi64(x3, 1)), + x86._mm_xor_si128( + x86._mm_srli_epi64(x3, 2), + x86._mm_srli_epi64(x3, 7)))) + x2 = x86._mm_xor_si128( + x86._mm_xor_si128( + x2, + x86._mm_slli_epi64(x3, 63)), + x86._mm_xor_si128( + x86._mm_slli_epi64(x3, 62), + x86._mm_slli_epi64(x3, 57))) + x0 = x86._mm_xor_si128( + x0, + x86._mm_xor_si128( + x86._mm_xor_si128( + x2, + x86._mm_srli_epi64(x2, 1)), + x86._mm_xor_si128( + x86._mm_srli_epi64(x2, 2), + x86._mm_srli_epi64(x2, 7)))) + x1 = x86._mm_xor_si128( + x86._mm_xor_si128( + x1, + x86._mm_slli_epi64(x2, 63)), + x86._mm_xor_si128( + x86._mm_slli_epi64(x2, 62), + x86._mm_slli_epi64(x2, 57))) + + return x0, x1 +} + +// Square value kw in GF(2^128) into (dw,dx). +@(private = "file", require_results, enable_target_feature = "sse2,pclmul") +square_f128 :: #force_inline proc "contextless" (kw: x86.__m128i) -> (x86.__m128i, x86.__m128i) { + z1 := x86._mm_clmulepi64_si128(kw, kw, 0x11) + z3 := x86._mm_clmulepi64_si128(kw, kw, 0x00) + z0 := x86._mm_shuffle_epi32(z1, 0x0E) + z2 := x86._mm_shuffle_epi32(z3, 0x0E) + z0, z1, z2, z3 = sl_256(z0, z1, z2, z3) + z0, z1 = reduce_f128(z0, z1, z2, z3) + return pbk(z0, z1) +} + +// ghash calculates the GHASH of data, with the key `key`, and input `dst` +// and `data`, and stores the resulting digest in `dst`. +// +// Note: `dst` is both an input and an output, to support easy implementation +// of GCM. +@(enable_target_feature = "sse2,ssse3,pclmul") +ghash :: proc "contextless" (dst, key, data: []byte) #no_bounds_check { + if len(dst) != _aes.GHASH_BLOCK_SIZE || len(key) != _aes.GHASH_BLOCK_SIZE { + intrinsics.trap() + } + + // Note: BearSSL opts to copy the remainder into a zero-filled + // 64-byte buffer. We do something slightly more simple. + + // Load key and dst (h and y). + yw := intrinsics.unaligned_load((^x86.__m128i)(raw_data(dst))) + h1w := intrinsics.unaligned_load((^x86.__m128i)(raw_data(key))) + yw = byteswap(yw) + h1w = byteswap(h1w) + h1x := bk(h1w) + + // Process 4 blocks at a time + buf := data + l := len(buf) + if l >= GHASH_STRIDE_BYTES_HW { + // Compute h2 = h^2 + h2w, h2x := square_f128(h1w) + + // Compute h3 = h^3 = h*(h^2) + t1 := x86._mm_clmulepi64_si128(h1w, h2w, 0x11) + t3 := x86._mm_clmulepi64_si128(h1w, h2w, 0x00) + t2 := x86._mm_xor_si128( + x86._mm_clmulepi64_si128(h1x, h2x, 0x00), + x86._mm_xor_si128(t1, t3)) + t0 := x86._mm_shuffle_epi32(t1, 0x0E) + t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E)) + t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E)) + t0, t1, t2, t3 = sl_256(t0, t1, t2, t3) + t0, t1 = reduce_f128(t0, t1, t2, t3) + h3w, h3x := pbk(t0, t1) + + // Compute h4 = h^4 = (h^2)^2 + h4w, h4x := square_f128(h2w) + + for l >= GHASH_STRIDE_BYTES_HW { + aw0 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf))) + aw1 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[16:]))) + aw2 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[32:]))) + aw3 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[48:]))) + aw0 = byteswap(aw0) + aw1 = byteswap(aw1) + aw2 = byteswap(aw2) + aw3 = byteswap(aw3) + buf, l = buf[GHASH_STRIDE_BYTES_HW:], l - GHASH_STRIDE_BYTES_HW + + aw0 = x86._mm_xor_si128(aw0, yw) + ax1 := bk(aw1) + ax2 := bk(aw2) + ax3 := bk(aw3) + ax0 := bk(aw0) + + t1 = x86._mm_xor_si128( + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(aw0, h4w, 0x11), + x86._mm_clmulepi64_si128(aw1, h3w, 0x11)), + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(aw2, h2w, 0x11), + x86._mm_clmulepi64_si128(aw3, h1w, 0x11))) + t3 = x86._mm_xor_si128( + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(aw0, h4w, 0x00), + x86._mm_clmulepi64_si128(aw1, h3w, 0x00)), + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(aw2, h2w, 0x00), + x86._mm_clmulepi64_si128(aw3, h1w, 0x00))) + t2 = x86._mm_xor_si128( + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(ax0, h4x, 0x00), + x86._mm_clmulepi64_si128(ax1, h3x, 0x00)), + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(ax2, h2x, 0x00), + x86._mm_clmulepi64_si128(ax3, h1x, 0x00))) + t2 = x86._mm_xor_si128(t2, x86._mm_xor_si128(t1, t3)) + t0 = x86._mm_shuffle_epi32(t1, 0x0E) + t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E)) + t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E)) + t0, t1, t2, t3 = sl_256(t0, t1, t2, t3) + t0, t1 = reduce_f128(t0, t1, t2, t3) + yw = x86._mm_unpacklo_epi64(t1, t0) + } + } + + // Process 1 block at a time + src: []byte + for l > 0 { + if l >= _aes.GHASH_BLOCK_SIZE { + src = buf + buf = buf[_aes.GHASH_BLOCK_SIZE:] + l -= _aes.GHASH_BLOCK_SIZE + } else { + tmp: [_aes.GHASH_BLOCK_SIZE]byte + copy(tmp[:], buf) + src = tmp[:] + l = 0 + } + + aw := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src))) + aw = byteswap(aw) + + aw = x86._mm_xor_si128(aw, yw) + ax := bk(aw) + + t1 := x86._mm_clmulepi64_si128(aw, h1w, 0x11) + t3 := x86._mm_clmulepi64_si128(aw, h1w, 0x00) + t2 := x86._mm_clmulepi64_si128(ax, h1x, 0x00) + t2 = x86._mm_xor_si128(t2, x86._mm_xor_si128(t1, t3)) + t0 := x86._mm_shuffle_epi32(t1, 0x0E) + t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E)) + t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E)) + t0, t1, t2, t3 = sl_256(t0, t1, t2, t3) + t0, t1 = reduce_f128(t0, t1, t2, t3) + yw = x86._mm_unpacklo_epi64(t1, t0) + } + + // Write back the hash (dst, aka y) + yw = byteswap(yw) + intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), yw) +} diff --git a/core/crypto/_aes/hw_intel/hw_intel_keysched.odin b/core/crypto/_aes/hw_intel/hw_intel_keysched.odin new file mode 100644 index 000000000..911dffbd5 --- /dev/null +++ b/core/crypto/_aes/hw_intel/hw_intel_keysched.odin @@ -0,0 +1,178 @@ +// Copyright (c) 2017 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//+build amd64 +package aes_hw_intel + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:mem" +import "core:simd/x86" + +// Intel AES-NI based implementation. Inspiration taken from BearSSL. +// +// Note: This assumes that the SROA optimization pass is enabled to be +// anything resembling performat otherwise, LLVM will not elide a massive +// number of redundant loads/stores it generates for every intrinsic call. + +@(private = "file", require_results, enable_target_feature = "sse2") +expand_step128 :: #force_inline proc(k1, k2: x86.__m128i) -> x86.__m128i { + k1, k2 := k1, k2 + + k2 = x86._mm_shuffle_epi32(k2, 0xff) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + return x86._mm_xor_si128(k1, k2) +} + +@(private = "file", require_results, enable_target_feature = "sse,sse2") +expand_step192a :: #force_inline proc (k1_, k2_: ^x86.__m128i, k3: x86.__m128i) -> (x86.__m128i, x86.__m128i) { + k1, k2, k3 := k1_^, k2_^, k3 + + k3 = x86._mm_shuffle_epi32(k3, 0x55) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, k3) + + tmp := k2 + k2 = x86._mm_xor_si128(k2, x86._mm_slli_si128(k2, 0x04)) + k2 = x86._mm_xor_si128(k2, x86._mm_shuffle_epi32(k1, 0xff)) + + k1_, k2_ := k1_, k2_ + k1_^, k2_^ = k1, k2 + + r1 := transmute(x86.__m128i)(x86._mm_shuffle_ps(transmute(x86.__m128)(tmp), transmute(x86.__m128)(k1), 0x44)) + r2 := transmute(x86.__m128i)(x86._mm_shuffle_ps(transmute(x86.__m128)(k1), transmute(x86.__m128)(k2), 0x4e)) + + return r1, r2 +} + +@(private = "file", require_results, enable_target_feature = "sse2") +expand_step192b :: #force_inline proc (k1_, k2_: ^x86.__m128i, k3: x86.__m128i) -> x86.__m128i { + k1, k2, k3 := k1_^, k2_^, k3 + + k3 = x86._mm_shuffle_epi32(k3, 0x55) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, k3) + + k2 = x86._mm_xor_si128(k2, x86._mm_slli_si128(k2, 0x04)) + k2 = x86._mm_xor_si128(k2, x86._mm_shuffle_epi32(k1, 0xff)) + + k1_, k2_ := k1_, k2_ + k1_^, k2_^ = k1, k2 + + return k1 +} + +@(private = "file", require_results, enable_target_feature = "sse2") +expand_step256b :: #force_inline proc(k1, k2: x86.__m128i) -> x86.__m128i { + k1, k2 := k1, k2 + + k2 = x86._mm_shuffle_epi32(k2, 0xaa) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + return x86._mm_xor_si128(k1, k2) +} + +@(private = "file", enable_target_feature = "aes") +derive_dec_keys :: proc(ctx: ^Context, sks: ^[15]x86.__m128i, num_rounds: int) { + intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[0]), sks[num_rounds]) + for i in 1 ..< num_rounds { + tmp := x86._mm_aesimc_si128(sks[i]) + intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[num_rounds - i]), tmp) + } + intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[num_rounds]), sks[0]) +} + +@(private, enable_target_feature = "sse,sse2,aes") +keysched :: proc(ctx: ^Context, key: []byte) { + sks: [15]x86.__m128i = --- + + // Compute the encryption keys. + num_rounds, key_len := 0, len(key) + switch key_len { + case _aes.KEY_SIZE_128: + sks[0] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key))) + sks[1] = expand_step128(sks[0], x86._mm_aeskeygenassist_si128(sks[0], 0x01)) + sks[2] = expand_step128(sks[1], x86._mm_aeskeygenassist_si128(sks[1], 0x02)) + sks[3] = expand_step128(sks[2], x86._mm_aeskeygenassist_si128(sks[2], 0x04)) + sks[4] = expand_step128(sks[3], x86._mm_aeskeygenassist_si128(sks[3], 0x08)) + sks[5] = expand_step128(sks[4], x86._mm_aeskeygenassist_si128(sks[4], 0x10)) + sks[6] = expand_step128(sks[5], x86._mm_aeskeygenassist_si128(sks[5], 0x20)) + sks[7] = expand_step128(sks[6], x86._mm_aeskeygenassist_si128(sks[6], 0x40)) + sks[8] = expand_step128(sks[7], x86._mm_aeskeygenassist_si128(sks[7], 0x80)) + sks[9] = expand_step128(sks[8], x86._mm_aeskeygenassist_si128(sks[8], 0x1b)) + sks[10] = expand_step128(sks[9], x86._mm_aeskeygenassist_si128(sks[9], 0x36)) + num_rounds = _aes.ROUNDS_128 + case _aes.KEY_SIZE_192: + k0 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(key))) + k1 := x86.__m128i{ + intrinsics.unaligned_load((^i64)(raw_data(key[16:]))), + 0, + } + sks[0] = k0 + sks[1], sks[2] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x01)) + sks[3] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x02)) + sks[4], sks[5] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x04)) + sks[6] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x08)) + sks[7], sks[8] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x10)) + sks[9] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x20)) + sks[10], sks[11] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x40)) + sks[12] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x80)) + num_rounds = _aes.ROUNDS_192 + case _aes.KEY_SIZE_256: + sks[0] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key))) + sks[1] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key[16:]))) + sks[2] = expand_step128(sks[0], x86._mm_aeskeygenassist_si128(sks[1], 0x01)) + sks[3] = expand_step256b(sks[1], x86._mm_aeskeygenassist_si128(sks[2], 0x01)) + sks[4] = expand_step128(sks[2], x86._mm_aeskeygenassist_si128(sks[3], 0x02)) + sks[5] = expand_step256b(sks[3], x86._mm_aeskeygenassist_si128(sks[4], 0x02)) + sks[6] = expand_step128(sks[4], x86._mm_aeskeygenassist_si128(sks[5], 0x04)) + sks[7] = expand_step256b(sks[5], x86._mm_aeskeygenassist_si128(sks[6], 0x04)) + sks[8] = expand_step128(sks[6], x86._mm_aeskeygenassist_si128(sks[7], 0x08)) + sks[9] = expand_step256b(sks[7], x86._mm_aeskeygenassist_si128(sks[8], 0x08)) + sks[10] = expand_step128(sks[8], x86._mm_aeskeygenassist_si128(sks[9], 0x10)) + sks[11] = expand_step256b(sks[9], x86._mm_aeskeygenassist_si128(sks[10], 0x10)) + sks[12] = expand_step128(sks[10], x86._mm_aeskeygenassist_si128(sks[11], 0x20)) + sks[13] = expand_step256b(sks[11], x86._mm_aeskeygenassist_si128(sks[12], 0x20)) + sks[14] = expand_step128(sks[12], x86._mm_aeskeygenassist_si128(sks[13], 0x40)) + num_rounds = _aes.ROUNDS_256 + case: + panic("crypto/aes: invalid AES key size") + } + for i in 0 ..= num_rounds { + intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_enc[i]), sks[i]) + } + + // Compute the decryption keys. GCM and CTR do not need this, however + // ECB, CBC, OCB3, etc do. + derive_dec_keys(ctx, &sks, num_rounds) + + ctx._num_rounds = num_rounds + + mem.zero_explicit(&sks, size_of(sks)) +} diff --git a/core/crypto/_edwards25519/edwards25519.odin b/core/crypto/_edwards25519/edwards25519.odin index 952bb9ef8..6495f7a3a 100644 --- a/core/crypto/_edwards25519/edwards25519.odin +++ b/core/crypto/_edwards25519/edwards25519.odin @@ -110,7 +110,7 @@ ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool { if len(b) != 32 { intrinsics.trap() } - b_ := transmute(^[32]byte)(raw_data(b)) + b_ := (^[32]byte)(raw_data(b)) // Do the work in a scratch element, so that ge is unchanged on // failure. @@ -169,7 +169,7 @@ ge_bytes :: proc "contextless" (ge: ^Group_Element, dst: []byte) { if len(dst) != 32 { intrinsics.trap() } - dst_ := transmute(^[32]byte)(raw_data(dst)) + dst_ := (^[32]byte)(raw_data(dst)) // Convert the element to affine (x, y) representation. x, y, z_inv: field.Tight_Field_Element = ---, ---, --- diff --git a/core/crypto/_edwards25519/edwards25519_scalar.odin b/core/crypto/_edwards25519/edwards25519_scalar.odin index 2644fe5f7..e21fa3755 100644 --- a/core/crypto/_edwards25519/edwards25519_scalar.odin +++ b/core/crypto/_edwards25519/edwards25519_scalar.odin @@ -28,7 +28,7 @@ sc_set_bytes :: proc "contextless" (sc: ^Scalar, b: []byte) -> bool { if len(b) != 32 { intrinsics.trap() } - b_ := transmute(^[32]byte)(raw_data(b)) + b_ := (^[32]byte)(raw_data(b)) return field.fe_from_bytes(sc, b_) } @@ -36,7 +36,7 @@ sc_set_bytes_rfc8032 :: proc "contextless" (sc: ^Scalar, b: []byte) { if len(b) != 32 { intrinsics.trap() } - b_ := transmute(^[32]byte)(raw_data(b)) + b_ := (^[32]byte)(raw_data(b)) field.fe_from_bytes_rfc8032(sc, b_) } diff --git a/core/crypto/_fiat/field_curve25519/field.odin b/core/crypto/_fiat/field_curve25519/field.odin index 8a8202ac4..04fc87659 100644 --- a/core/crypto/_fiat/field_curve25519/field.odin +++ b/core/crypto/_fiat/field_curve25519/field.odin @@ -6,13 +6,13 @@ import "core:mem" fe_relax_cast :: #force_inline proc "contextless" ( arg1: ^Tight_Field_Element, ) -> ^Loose_Field_Element { - return transmute(^Loose_Field_Element)(arg1) + return (^Loose_Field_Element)(arg1) } fe_tighten_cast :: #force_inline proc "contextless" ( arg1: ^Loose_Field_Element, ) -> ^Tight_Field_Element { - return transmute(^Tight_Field_Element)(arg1) + return (^Tight_Field_Element)(arg1) } fe_clear :: proc "contextless" ( diff --git a/core/crypto/_fiat/field_poly1305/field.odin b/core/crypto/_fiat/field_poly1305/field.odin index c50a56b0c..b12046858 100644 --- a/core/crypto/_fiat/field_poly1305/field.odin +++ b/core/crypto/_fiat/field_poly1305/field.odin @@ -7,13 +7,13 @@ import "core:mem" fe_relax_cast :: #force_inline proc "contextless" ( arg1: ^Tight_Field_Element, ) -> ^Loose_Field_Element { - return transmute(^Loose_Field_Element)(arg1) + return (^Loose_Field_Element)(arg1) } fe_tighten_cast :: #force_inline proc "contextless" ( arg1: ^Loose_Field_Element, ) -> ^Tight_Field_Element { - return transmute(^Tight_Field_Element)(arg1) + return (^Tight_Field_Element)(arg1) } fe_from_bytes :: #force_inline proc "contextless" ( diff --git a/core/crypto/aes/aes.odin b/core/crypto/aes/aes.odin new file mode 100644 index 000000000..ef305fd21 --- /dev/null +++ b/core/crypto/aes/aes.odin @@ -0,0 +1,21 @@ +/* +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 +*/ +package aes + +import "core:crypto/_aes" + +// KEY_SIZE_128 is the AES-128 key size in bytes. +KEY_SIZE_128 :: _aes.KEY_SIZE_128 +// KEY_SIZE_192 is the AES-192 key size in bytes. +KEY_SIZE_192 :: _aes.KEY_SIZE_192 +// KEY_SIZE_256 is the AES-256 key size in bytes. +KEY_SIZE_256 :: _aes.KEY_SIZE_256 + +// BLOCK_SIZE is the AES block size in bytes. +BLOCK_SIZE :: _aes.BLOCK_SIZE diff --git a/core/crypto/aes/aes_ctr.odin b/core/crypto/aes/aes_ctr.odin new file mode 100644 index 000000000..1c5fe31e8 --- /dev/null +++ b/core/crypto/aes/aes_ctr.odin @@ -0,0 +1,201 @@ +package aes + +import "core:bytes" +import "core:crypto/_aes/ct64" +import "core:encoding/endian" +import "core:math/bits" +import "core:mem" + +// CTR_IV_SIZE is the size of the CTR mode IV in bytes. +CTR_IV_SIZE :: 16 + +// Context_CTR is a keyed AES-CTR instance. +Context_CTR :: struct { + _impl: Context_Impl, + _buffer: [BLOCK_SIZE]byte, + _off: int, + _ctr_hi: u64, + _ctr_lo: u64, + _is_initialized: bool, +} + +// init_ctr initializes a Context_CTR with the provided key and IV. +init_ctr :: proc(ctx: ^Context_CTR, key, iv: []byte, impl := Implementation.Hardware) { + if len(iv) != CTR_IV_SIZE { + panic("crypto/aes: invalid CTR IV size") + } + + init_impl(&ctx._impl, key, impl) + ctx._off = BLOCK_SIZE + ctx._ctr_hi = endian.unchecked_get_u64be(iv[0:]) + ctx._ctr_lo = endian.unchecked_get_u64be(iv[8:]) + ctx._is_initialized = true +} + +// xor_bytes_ctr XORs each byte in src with bytes taken from the AES-CTR +// keystream, and writes the resulting output to dst. dst and src MUST +// alias exactly or not at all. +xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) { + assert(ctx._is_initialized) + + src, dst := src, dst + if dst_len := len(dst); dst_len < len(src) { + src = src[:dst_len] + } + + if bytes.alias_inexactly(dst, src) { + panic("crypto/aes: dst and src alias inexactly") + } + + for remaining := len(src); remaining > 0; { + // Process multiple blocks at once + if ctx._off == BLOCK_SIZE { + if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 { + direct_bytes := nr_blocks * BLOCK_SIZE + ctr_blocks(ctx, dst, src, nr_blocks) + remaining -= direct_bytes + if remaining == 0 { + return + } + dst = dst[direct_bytes:] + src = src[direct_bytes:] + } + + // If there is a partial block, generate and buffer 1 block + // worth of keystream. + ctr_blocks(ctx, ctx._buffer[:], nil, 1) + ctx._off = 0 + } + + // Process partial blocks from the buffered keystream. + to_xor := min(BLOCK_SIZE - ctx._off, remaining) + buffered_keystream := ctx._buffer[ctx._off:] + for i := 0; i < to_xor; i = i + 1 { + dst[i] = buffered_keystream[i] ~ src[i] + } + ctx._off += to_xor + dst = dst[to_xor:] + src = src[to_xor:] + remaining -= to_xor + } +} + +// keystream_bytes_ctr fills dst with the raw AES-CTR keystream output. +keystream_bytes_ctr :: proc(ctx: ^Context_CTR, dst: []byte) { + assert(ctx._is_initialized) + + dst := dst + 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 + ctr_blocks(ctx, dst, nil, nr_blocks) + remaining -= direct_bytes + if remaining == 0 { + return + } + dst = dst[direct_bytes:] + } + + // If there is a partial block, generate and buffer 1 block + // worth of keystream. + ctr_blocks(ctx, ctx._buffer[:], nil, 1) + ctx._off = 0 + } + + // Process partial blocks from the buffered keystream. + to_copy := min(BLOCK_SIZE - ctx._off, remaining) + buffered_keystream := ctx._buffer[ctx._off:] + copy(dst[:to_copy], buffered_keystream[:to_copy]) + ctx._off += to_copy + dst = dst[to_copy:] + remaining -= to_copy + } +} + +// reset_ctr sanitizes the Context_CTR. The Context_CTR must be +// re-initialized to be used again. +reset_ctr :: proc "contextless" (ctx: ^Context_CTR) { + reset_impl(&ctx._impl) + ctx._off = 0 + ctx._ctr_hi = 0 + ctx._ctr_lo = 0 + mem.zero_explicit(&ctx._buffer, size_of(ctx._buffer)) + ctx._is_initialized = false +} + +@(private = "file") +ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) #no_bounds_check { + // Use the optimized hardware implementation if available. + if _, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { + ctr_blocks_hw(ctx, dst, src, nr_blocks) + return + } + + // Portable implementation. + ct64_inc_ctr := #force_inline proc "contextless" (dst: []byte, hi, lo: u64) -> (u64, u64) { + endian.unchecked_put_u64be(dst[0:], hi) + endian.unchecked_put_u64be(dst[8:], lo) + + hi, lo := hi, lo + carry: u64 + lo, carry = bits.add_u64(lo, 1, 0) + hi, _ = bits.add_u64(hi, 0, carry) + return hi, lo + } + + impl := &ctx._impl.(ct64.Context) + src, dst := src, dst + nr_blocks := nr_blocks + ctr_hi, ctr_lo := ctx._ctr_hi, ctx._ctr_lo + + tmp: [ct64.STRIDE][BLOCK_SIZE]byte = --- + ctrs: [ct64.STRIDE][]byte = --- + for i in 0 ..< ct64.STRIDE { + ctrs[i] = tmp[i][:] + } + for nr_blocks > 0 { + n := min(ct64.STRIDE, nr_blocks) + blocks := ctrs[:n] + + for i in 0 ..< n { + ctr_hi, ctr_lo = ct64_inc_ctr(blocks[i], ctr_hi, ctr_lo) + } + ct64.encrypt_blocks(impl, blocks, blocks) + + xor_blocks(dst, src, blocks) + + if src != nil { + src = src[n * BLOCK_SIZE:] + } + dst = dst[n * BLOCK_SIZE:] + nr_blocks -= n + } + + // Write back the counter. + ctx._ctr_hi, ctx._ctr_lo = ctr_hi, ctr_lo + + mem.zero_explicit(&tmp, size_of(tmp)) +} + +@(private) +xor_blocks :: #force_inline proc "contextless" (dst, src: []byte, blocks: [][]byte) { + // Note: This would be faster `core:simd` was used, however if + // performance of this implementation matters to where that + // optimization would be worth it, use chacha20poly1305, or a + // CPU that isn't e-waste. + #no_bounds_check { + if src != nil { + for i in 0 ..< len(blocks) { + off := i * BLOCK_SIZE + for j in 0 ..< BLOCK_SIZE { + blocks[i][j] ~= src[off + j] + } + } + } + for i in 0 ..< len(blocks) { + copy(dst[i * BLOCK_SIZE:], blocks[i]) + } + } +} diff --git a/core/crypto/aes/aes_ctr_hw_intel.odin b/core/crypto/aes/aes_ctr_hw_intel.odin new file mode 100644 index 000000000..1c9e815ad --- /dev/null +++ b/core/crypto/aes/aes_ctr_hw_intel.odin @@ -0,0 +1,151 @@ +//+build amd64 +package aes + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:math/bits" +import "core:mem" +import "core:simd/x86" + +@(private) +CTR_STRIDE_HW :: 4 +@(private) +CTR_STRIDE_BYTES_HW :: CTR_STRIDE_HW * BLOCK_SIZE + +@(private, enable_target_feature = "sse2,aes") +ctr_blocks_hw :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) #no_bounds_check { + hw_ctx := ctx._impl.(Context_Impl_Hardware) + + sks: [15]x86.__m128i = --- + for i in 0 ..= hw_ctx._num_rounds { + sks[i] = intrinsics.unaligned_load((^x86.__m128i)(&hw_ctx._sk_exp_enc[i])) + } + + hw_inc_ctr := #force_inline proc "contextless" (hi, lo: u64) -> (x86.__m128i, u64, u64) { + ret := x86.__m128i{ + i64(intrinsics.byte_swap(hi)), + i64(intrinsics.byte_swap(lo)), + } + + hi, lo := hi, lo + carry: u64 + + lo, carry = bits.add_u64(lo, 1, 0) + hi, _ = bits.add_u64(hi, 0, carry) + return ret, hi, lo + } + + // The latency of AESENC depends on mfg and microarchitecture: + // - 7 -> up to Broadwell + // - 4 -> AMD and Skylake - Cascade Lake + // - 3 -> Ice Lake and newer + // + // This implementation does 4 blocks at once, since performance + // should be "adequate" across most CPUs. + + src, dst := src, dst + nr_blocks := nr_blocks + ctr_hi, ctr_lo := ctx._ctr_hi, ctx._ctr_lo + + blks: [CTR_STRIDE_HW]x86.__m128i = --- + for nr_blocks >= CTR_STRIDE_HW { + #unroll for i in 0..< CTR_STRIDE_HW { + blks[i], ctr_hi, ctr_lo = hw_inc_ctr(ctr_hi, ctr_lo) + } + + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_xor_si128(blks[i], sks[0]) + } + #unroll for i in 1 ..= 9 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + switch hw_ctx._num_rounds { + case _aes.ROUNDS_128: + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[10]) + } + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[12]) + } + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[14]) + } + } + + xor_blocks_hw(dst, src, blks[:]) + + if src != nil { + src = src[CTR_STRIDE_BYTES_HW:] + } + dst = dst[CTR_STRIDE_BYTES_HW:] + nr_blocks -= CTR_STRIDE_HW + } + + // Handle the remainder. + for nr_blocks > 0 { + blks[0], ctr_hi, ctr_lo = hw_inc_ctr(ctr_hi, ctr_lo) + + blks[0] = x86._mm_xor_si128(blks[0], sks[0]) + #unroll for i in 1 ..= 9 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + switch hw_ctx._num_rounds { + case _aes.ROUNDS_128: + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[10]) + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[12]) + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[14]) + } + + xor_blocks_hw(dst, src, blks[:1]) + + if src != nil { + src = src[BLOCK_SIZE:] + } + dst = dst[BLOCK_SIZE:] + nr_blocks -= 1 + } + + // Write back the counter. + ctx._ctr_hi, ctx._ctr_lo = ctr_hi, ctr_lo + + mem.zero_explicit(&blks, size_of(blks)) + mem.zero_explicit(&sks, size_of(sks)) +} + +@(private, enable_target_feature = "sse2") +xor_blocks_hw :: proc(dst, src: []byte, blocks: []x86.__m128i) { + #no_bounds_check { + if src != nil { + for i in 0 ..< len(blocks) { + off := i * BLOCK_SIZE + tmp := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src[off:]))) + blocks[i] = x86._mm_xor_si128(blocks[i], tmp) + } + } + for i in 0 ..< len(blocks) { + intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst[i * BLOCK_SIZE:])), blocks[i]) + } + } +} diff --git a/core/crypto/aes/aes_ecb.odin b/core/crypto/aes/aes_ecb.odin new file mode 100644 index 000000000..498429e29 --- /dev/null +++ b/core/crypto/aes/aes_ecb.odin @@ -0,0 +1,57 @@ +package aes + +import "core:crypto/_aes/ct64" + +// Context_ECB is a keyed AES-ECB instance. +// +// WARNING: Using ECB mode is strongly discouraged unless it is being +// used to implement higher level constructs. +Context_ECB :: struct { + _impl: Context_Impl, + _is_initialized: bool, +} + +// init_ecb initializes a Context_ECB with the provided key. +init_ecb :: proc(ctx: ^Context_ECB, key: []byte, impl := Implementation.Hardware) { + init_impl(&ctx._impl, key, impl) + ctx._is_initialized = true +} + +// encrypt_ecb encrypts the BLOCK_SIZE buffer src, and writes the result to dst. +encrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) { + assert(ctx._is_initialized) + + if len(dst) != BLOCK_SIZE || len(src) != BLOCK_SIZE { + panic("crypto/aes: invalid buffer size(s)") + } + + switch &impl in ctx._impl { + case ct64.Context: + ct64.encrypt_block(&impl, dst, src) + case Context_Impl_Hardware: + encrypt_block_hw(&impl, dst, src) + } +} + +// decrypt_ecb decrypts the BLOCK_SIZE buffer src, and writes the result to dst. +decrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) { + assert(ctx._is_initialized) + + if len(dst) != BLOCK_SIZE || len(src) != BLOCK_SIZE { + panic("crypto/aes: invalid buffer size(s)") + } + + switch &impl in ctx._impl { + case ct64.Context: + ct64.decrypt_block(&impl, dst, src) + case Context_Impl_Hardware: + decrypt_block_hw(&impl, dst, src) + } +} + +// reset_ecb sanitizes the Context_ECB. The Context_ECB must be +// re-initialized to be used again. +reset_ecb :: proc "contextless" (ctx: ^Context_ECB) { + reset_impl(&ctx._impl) + ctx._is_initialized = false +} diff --git a/core/crypto/aes/aes_ecb_hw_intel.odin b/core/crypto/aes/aes_ecb_hw_intel.odin new file mode 100644 index 000000000..b2ff36a0c --- /dev/null +++ b/core/crypto/aes/aes_ecb_hw_intel.odin @@ -0,0 +1,58 @@ +//+build amd64 +package aes + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:simd/x86" + +@(private, enable_target_feature = "sse2,aes") +encrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) { + blk := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src))) + + blk = x86._mm_xor_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[0]))) + #unroll for i in 1 ..= 9 { + blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i]))) + } + switch ctx._num_rounds { + case _aes.ROUNDS_128: + blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[10]))) + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i]))) + } + blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[12]))) + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i]))) + } + blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[14]))) + } + + intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), blk) +} + +@(private, enable_target_feature = "sse2,aes") +decrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) { + blk := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src))) + + blk = x86._mm_xor_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[0]))) + #unroll for i in 1 ..= 9 { + blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i]))) + } + switch ctx._num_rounds { + case _aes.ROUNDS_128: + blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[10]))) + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i]))) + } + blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[12]))) + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i]))) + } + blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[14]))) + } + + intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), blk) +} diff --git a/core/crypto/aes/aes_gcm.odin b/core/crypto/aes/aes_gcm.odin new file mode 100644 index 000000000..25e0cc35b --- /dev/null +++ b/core/crypto/aes/aes_gcm.odin @@ -0,0 +1,269 @@ +package aes + +import "core:bytes" +import "core:crypto" +import "core:crypto/_aes" +import "core:crypto/_aes/ct64" +import "core:encoding/endian" +import "core:mem" + +// GCM_NONCE_SIZE is the default size of the GCM nonce in bytes. +GCM_NONCE_SIZE :: 12 +// GCM_NONCE_SIZE_MAX is the maximum size of the GCM nonce in bytes. +GCM_NONCE_SIZE_MAX :: 0x2000000000000000 // floor((2^64 - 1) / 8) bits +// GCM_TAG_SIZE is the size of a GCM tag in bytes. +GCM_TAG_SIZE :: _aes.GHASH_TAG_SIZE + +@(private) +GCM_A_MAX :: max(u64) / 8 // 2^64 - 1 bits -> bytes +@(private) +GCM_P_MAX :: 0xfffffffe0 // 2^39 - 256 bits -> bytes + +// Context_GCM is a keyed AES-GCM instance. +Context_GCM :: struct { + _impl: Context_Impl, + _is_initialized: bool, +} + +// init_gcm initializes a Context_GCM with the provided key. +init_gcm :: proc(ctx: ^Context_GCM, key: []byte, impl := Implementation.Hardware) { + 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. +// +// dst and plaintext MUST alias exactly or not at all. +seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) { + assert(ctx._is_initialized) + + gcm_validate_common_slice_sizes(tag, nonce, aad, plaintext) + if len(dst) != len(plaintext) { + panic("crypto/aes: invalid destination ciphertext size") + } + if bytes.alias_inexactly(dst, plaintext) { + panic("crypto/aes: dst and plaintext alias inexactly") + } + + if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { + gcm_seal_hw(&impl, dst, tag, nonce, aad, plaintext) + return + } + + 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) + + // Note: Our GHASH implementation handles appending padding. + ct64.ghash(s[:], h[:], aad) + gctr_ct64(ctx, dst, &s, plaintext, &h, &j0, true) + final_ghash_ct64(&s, &h, &j0_enc, len(aad), len(plaintext)) + copy(tag, s[:]) + + mem.zero_explicit(&h, len(h)) + mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&j0_enc, len(j0_enc)) +} + +// open_gcm authenticates the aad and ciphertext, and decrypts the ciphertext, +// with the provided Context_GCM, nonce, 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 { + assert(ctx._is_initialized) + + gcm_validate_common_slice_sizes(tag, nonce, aad, ciphertext) + if len(dst) != len(ciphertext) { + panic("crypto/aes: invalid destination plaintext size") + } + if bytes.alias_inexactly(dst, ciphertext) { + panic("crypto/aes: dst and ciphertext alias inexactly") + } + + if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { + return gcm_open_hw(&impl, dst, nonce, aad, ciphertext, tag) + } + + 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) + + ct64.ghash(s[:], h[:], aad) + gctr_ct64(ctx, dst, &s, ciphertext, &h, &j0, false) + final_ghash_ct64(&s, &h, &j0_enc, len(aad), len(ciphertext)) + + ok := crypto.compare_constant_time(s[:], tag) == 1 + if !ok { + mem.zero_explicit(raw_data(dst), len(dst)) + } + + mem.zero_explicit(&h, len(h)) + mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&j0_enc, len(j0_enc)) + mem.zero_explicit(&s, len(s)) + + return ok +} + +// reset_ctr 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) + ctx._is_initialized = false +} + +@(private = "file") +gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) { + if len(tag) != GCM_TAG_SIZE { + panic("crypto/aes: invalid GCM tag size") + } + + // The specification supports nonces in the range [1, 2^64) bits. + if l := len(nonce); l == 0 || u64(l) >= GCM_NONCE_SIZE_MAX { + panic("crypto/aes: invalid GCM nonce size") + } + + if aad_len := u64(len(aad)); aad_len > GCM_A_MAX { + panic("crypto/aes: oversized GCM aad") + } + if text_len := u64(len(text)); text_len > GCM_P_MAX { + panic("crypto/aes: oversized GCM src data") + } +} + +@(private = "file") +init_ghash_ct64 :: proc( + ctx: ^Context_GCM, + h: ^[_aes.GHASH_KEY_SIZE]byte, + j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte, + nonce: []byte, +) { + impl := &ctx._impl.(ct64.Context) + + // 1. Let H = CIPH(k, 0^128) + ct64.encrypt_block(impl, h[:], h[:]) + + // Define a block, J0, as follows: + if l := len(nonce); l == GCM_NONCE_SIZE { + // if len(IV) = 96, then let J0 = IV || 0^31 || 1 + copy(j0[:], nonce) + j0[_aes.GHASH_BLOCK_SIZE - 1] = 1 + } else { + // If len(IV) != 96, then let s = 128 ceil(len(IV)/128) - len(IV), + // and let J0 = GHASHH(IV || 0^(s+64) || ceil(len(IV))^64). + ct64.ghash(j0[:], h[:], nonce) + + tmp: [_aes.GHASH_BLOCK_SIZE]byte + endian.unchecked_put_u64be(tmp[8:], u64(l) * 8) + ct64.ghash(j0[:], h[:], tmp[:]) + } + + // ECB encrypt j0, so that we can just XOR with the tag. In theory + // this could be processed along with the final GCTR block, to + // potentially save a call to AES-ECB, but... just use AES-NI. + ct64.encrypt_block(impl, j0_enc[:], j0[:]) +} + +@(private = "file") +final_ghash_ct64 :: proc( + s: ^[_aes.GHASH_BLOCK_SIZE]byte, + h: ^[_aes.GHASH_KEY_SIZE]byte, + j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + a_len: int, + t_len: int, +) { + blk: [_aes.GHASH_BLOCK_SIZE]byte + endian.unchecked_put_u64be(blk[0:], u64(a_len) * 8) + endian.unchecked_put_u64be(blk[8:], u64(t_len) * 8) + + ct64.ghash(s[:], h[:], blk[:]) + for i in 0 ..< len(s) { + s[i] ~= j0[i] + } +} + +@(private = "file") +gctr_ct64 :: proc( + ctx: ^Context_GCM, + dst: []byte, + s: ^[_aes.GHASH_BLOCK_SIZE]byte, + src: []byte, + h: ^[_aes.GHASH_KEY_SIZE]byte, + nonce: ^[_aes.GHASH_BLOCK_SIZE]byte, + is_seal: bool, +) #no_bounds_check { + ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 { + endian.unchecked_put_u32be(dst[12:], ctr) + return ctr + 1 + } + + // 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 + 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]) + } + + impl := &ctx._impl.(ct64.Context) + src, dst := src, dst + + nr_blocks := len(src) / BLOCK_SIZE + for nr_blocks > 0 { + n := min(ct64.STRIDE, nr_blocks) + l := n * BLOCK_SIZE + + if !is_seal { + ct64.ghash(s[:], h[:], src[:l]) + } + + // The keystream is written to a separate buffer, as we will + // reuse the first 96-bits of each counter. + for i in 0 ..< n { + ctr = ct64_inc_ctr32(ctrs[i], ctr) + } + ct64.encrypt_blocks(impl, blks[:n], ctrs[:n]) + + xor_blocks(dst, src, blks[:n]) + + if is_seal { + ct64.ghash(s[:], h[:], dst[:l]) + } + + src = src[l:] + dst = dst[l:] + nr_blocks -= n + } + if l := len(src); l > 0 { + if !is_seal { + ct64.ghash(s[:], h[:], src[:l]) + } + + ct64_inc_ctr32(ctrs[0], ctr) + ct64.encrypt_block(impl, ctrs[0], ctrs[0]) + + for i in 0 ..< l { + dst[i] = src[i] ~ ctrs[0][i] + } + + if is_seal { + ct64.ghash(s[:], h[:], dst[:l]) + } + } + + mem.zero_explicit(&tmp, size_of(tmp)) + mem.zero_explicit(&tmp2, size_of(tmp2)) +} diff --git a/core/crypto/aes/aes_gcm_hw_intel.odin b/core/crypto/aes/aes_gcm_hw_intel.odin new file mode 100644 index 000000000..7d32d4d96 --- /dev/null +++ b/core/crypto/aes/aes_gcm_hw_intel.odin @@ -0,0 +1,243 @@ +//+build amd64 +package aes + +import "base:intrinsics" +import "core:crypto" +import "core:crypto/_aes" +import "core:crypto/_aes/hw_intel" +import "core:encoding/endian" +import "core:mem" +import "core:simd/x86" + +@(private) +gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) { + h: [_aes.GHASH_KEY_SIZE]byte + j0: [_aes.GHASH_BLOCK_SIZE]byte + j0_enc: [_aes.GHASH_BLOCK_SIZE]byte + s: [_aes.GHASH_TAG_SIZE]byte + init_ghash_hw(ctx, &h, &j0, &j0_enc, nonce) + + // Note: Our GHASH implementation handles appending padding. + hw_intel.ghash(s[:], h[:], aad) + gctr_hw(ctx, dst, &s, plaintext, &h, &j0, true) + final_ghash_hw(&s, &h, &j0_enc, len(aad), len(plaintext)) + copy(tag, s[:]) + + mem.zero_explicit(&h, len(h)) + mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&j0_enc, len(j0_enc)) +} + +@(private) +gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool { + h: [_aes.GHASH_KEY_SIZE]byte + j0: [_aes.GHASH_BLOCK_SIZE]byte + j0_enc: [_aes.GHASH_BLOCK_SIZE]byte + s: [_aes.GHASH_TAG_SIZE]byte + init_ghash_hw(ctx, &h, &j0, &j0_enc, nonce) + + hw_intel.ghash(s[:], h[:], aad) + gctr_hw(ctx, dst, &s, ciphertext, &h, &j0, false) + final_ghash_hw(&s, &h, &j0_enc, len(aad), len(ciphertext)) + + ok := crypto.compare_constant_time(s[:], tag) == 1 + if !ok { + mem.zero_explicit(raw_data(dst), len(dst)) + } + + mem.zero_explicit(&h, len(h)) + mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&j0_enc, len(j0_enc)) + mem.zero_explicit(&s, len(s)) + + return ok +} + +@(private = "file") +init_ghash_hw :: proc( + ctx: ^Context_Impl_Hardware, + h: ^[_aes.GHASH_KEY_SIZE]byte, + j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte, + nonce: []byte, +) { + // 1. Let H = CIPH(k, 0^128) + encrypt_block_hw(ctx, h[:], h[:]) + + // Define a block, J0, as follows: + if l := len(nonce); l == GCM_NONCE_SIZE { + // if len(IV) = 96, then let J0 = IV || 0^31 || 1 + copy(j0[:], nonce) + j0[_aes.GHASH_BLOCK_SIZE - 1] = 1 + } else { + // If len(IV) != 96, then let s = 128 ceil(len(IV)/128) - len(IV), + // and let J0 = GHASHH(IV || 0^(s+64) || ceil(len(IV))^64). + hw_intel.ghash(j0[:], h[:], nonce) + + tmp: [_aes.GHASH_BLOCK_SIZE]byte + endian.unchecked_put_u64be(tmp[8:], u64(l) * 8) + hw_intel.ghash(j0[:], h[:], tmp[:]) + } + + // ECB encrypt j0, so that we can just XOR with the tag. + encrypt_block_hw(ctx, j0_enc[:], j0[:]) +} + +@(private = "file", enable_target_feature = "sse2") +final_ghash_hw :: proc( + s: ^[_aes.GHASH_BLOCK_SIZE]byte, + h: ^[_aes.GHASH_KEY_SIZE]byte, + j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + a_len: int, + t_len: int, +) { + blk: [_aes.GHASH_BLOCK_SIZE]byte + endian.unchecked_put_u64be(blk[0:], u64(a_len) * 8) + endian.unchecked_put_u64be(blk[8:], u64(t_len) * 8) + + hw_intel.ghash(s[:], h[:], blk[:]) + j0_vec := intrinsics.unaligned_load((^x86.__m128i)(j0)) + s_vec := intrinsics.unaligned_load((^x86.__m128i)(s)) + s_vec = x86._mm_xor_si128(s_vec, j0_vec) + intrinsics.unaligned_store((^x86.__m128i)(s), s_vec) +} + +@(private = "file", enable_target_feature = "sse2,sse4.1,aes") +gctr_hw :: proc( + ctx: ^Context_Impl_Hardware, + dst: []byte, + s: ^[_aes.GHASH_BLOCK_SIZE]byte, + src: []byte, + h: ^[_aes.GHASH_KEY_SIZE]byte, + nonce: ^[_aes.GHASH_BLOCK_SIZE]byte, + is_seal: bool, +) #no_bounds_check { + sks: [15]x86.__m128i = --- + for i in 0 ..= ctx._num_rounds { + sks[i] = intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i])) + } + + // Setup the counter block + ctr_blk := intrinsics.unaligned_load((^x86.__m128i)(nonce)) + ctr := endian.unchecked_get_u32be(nonce[GCM_NONCE_SIZE:]) + 1 + + src, dst := src, dst + + // Note: Instead of doing GHASH and CTR separately, it is more + // performant to interleave (stitch) the two operations together. + // This results in an unreadable mess, so we opt for simplicity + // as performance is adequate. + + blks: [CTR_STRIDE_HW]x86.__m128i = --- + nr_blocks := len(src) / BLOCK_SIZE + for nr_blocks >= CTR_STRIDE_HW { + if !is_seal { + hw_intel.ghash(s[:], h[:], src[:CTR_STRIDE_BYTES_HW]) + } + + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i], ctr = hw_inc_ctr32(&ctr_blk, ctr) + } + + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_xor_si128(blks[i], sks[0]) + } + #unroll for i in 1 ..= 9 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + switch ctx._num_rounds { + case _aes.ROUNDS_128: + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[10]) + } + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[12]) + } + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[14]) + } + } + + xor_blocks_hw(dst, src, blks[:]) + + if is_seal { + hw_intel.ghash(s[:], h[:], dst[:CTR_STRIDE_BYTES_HW]) + } + + src = src[CTR_STRIDE_BYTES_HW:] + dst = dst[CTR_STRIDE_BYTES_HW:] + nr_blocks -= CTR_STRIDE_HW + } + + // Handle the remainder. + for n := len(src); n > 0; { + l := min(n, BLOCK_SIZE) + if !is_seal { + hw_intel.ghash(s[:], h[:], src[:l]) + } + + blks[0], ctr = hw_inc_ctr32(&ctr_blk, ctr) + + blks[0] = x86._mm_xor_si128(blks[0], sks[0]) + #unroll for i in 1 ..= 9 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + switch ctx._num_rounds { + case _aes.ROUNDS_128: + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[10]) + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[12]) + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[14]) + } + + if l == BLOCK_SIZE { + xor_blocks_hw(dst, src, blks[:1]) + } else { + blk: [BLOCK_SIZE]byte + copy(blk[:], src) + xor_blocks_hw(blk[:], blk[:], blks[:1]) + copy(dst, blk[:l]) + } + if is_seal { + hw_intel.ghash(s[:], h[:], dst[:l]) + } + + dst = dst[l:] + src = src[l:] + n -= l + } + + mem.zero_explicit(&blks, size_of(blks)) + mem.zero_explicit(&sks, size_of(sks)) +} + +// BUG: Sticking this in gctr_hw (like the other implementations) crashes +// the compiler. +// +// src/check_expr.cpp(7892): Assertion Failure: `c->curr_proc_decl->entity` +@(private = "file", enable_target_feature = "sse4.1") +hw_inc_ctr32 :: #force_inline proc "contextless" (src: ^x86.__m128i, ctr: u32) -> (x86.__m128i, u32) { + ret := x86._mm_insert_epi32(src^, i32(intrinsics.byte_swap(ctr)), 3) + return ret, ctr + 1 +} diff --git a/core/crypto/aes/aes_impl.odin b/core/crypto/aes/aes_impl.odin new file mode 100644 index 000000000..03747f1fb --- /dev/null +++ b/core/crypto/aes/aes_impl.odin @@ -0,0 +1,41 @@ +package aes + +import "core:crypto/_aes/ct64" +import "core:mem" +import "core:reflect" + +@(private) +Context_Impl :: union { + ct64.Context, + Context_Impl_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()`). +Implementation :: enum { + Portable, + Hardware, +} + +@(private) +init_impl :: proc(ctx: ^Context_Impl, key: []byte, impl: Implementation) { + impl := impl + if !is_hardware_accelerated() { + impl = .Portable + } + + switch impl { + case .Portable: + reflect.set_union_variant_typeid(ctx^, typeid_of(ct64.Context)) + ct64.init(&ctx.(ct64.Context), key) + case .Hardware: + reflect.set_union_variant_typeid(ctx^, typeid_of(Context_Impl_Hardware)) + init_impl_hw(&ctx.(Context_Impl_Hardware), key) + } +} + +@(private) +reset_impl :: proc "contextless" (ctx: ^Context_Impl) { + mem.zero_explicit(ctx, size_of(Context_Impl)) +} diff --git a/core/crypto/aes/aes_impl_hw_gen.odin b/core/crypto/aes/aes_impl_hw_gen.odin new file mode 100644 index 000000000..5361c6ef0 --- /dev/null +++ b/core/crypto/aes/aes_impl_hw_gen.odin @@ -0,0 +1,44 @@ +//+build !amd64 +package aes + +@(private = "file") +ERR_HW_NOT_SUPPORTED :: "crypto/aes: hardware implementation unsupported" + +// is_hardware_accelerated returns true iff hardware accelerated AES +// is supported. +is_hardware_accelerated :: proc "contextless" () -> bool { + return false +} + +@(private) +Context_Impl_Hardware :: struct {} + +@(private) +init_impl_hw :: proc(ctx: ^Context_Impl_Hardware, key: []byte) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +encrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +decrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +ctr_blocks_hw :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool { + panic(ERR_HW_NOT_SUPPORTED) +} diff --git a/core/crypto/aes/aes_impl_hw_intel.odin b/core/crypto/aes/aes_impl_hw_intel.odin new file mode 100644 index 000000000..39ea2dc8d --- /dev/null +++ b/core/crypto/aes/aes_impl_hw_intel.odin @@ -0,0 +1,18 @@ +//+build amd64 +package aes + +import "core:crypto/_aes/hw_intel" + +// is_hardware_accelerated returns true iff hardware accelerated AES +// is supported. +is_hardware_accelerated :: proc "contextless" () -> bool { + return hw_intel.is_supported() +} + +@(private) +Context_Impl_Hardware :: hw_intel.Context + +@(private, enable_target_feature = "sse2,aes") +init_impl_hw :: proc(ctx: ^Context_Impl_Hardware, key: []byte) { + hw_intel.init(ctx, key) +} diff --git a/core/crypto/chacha20/chacha20.odin b/core/crypto/chacha20/chacha20.odin index 7f0950d03..73d3e1ea2 100644 --- a/core/crypto/chacha20/chacha20.odin +++ b/core/crypto/chacha20/chacha20.odin @@ -7,6 +7,7 @@ See: */ package chacha20 +import "core:bytes" import "core:encoding/endian" import "core:math/bits" import "core:mem" @@ -121,14 +122,15 @@ seek :: proc(ctx: ^Context, block_nr: u64) { xor_bytes :: proc(ctx: ^Context, dst, src: []byte) { assert(ctx._is_initialized) - // TODO: Enforcing that dst and src alias exactly or not at all - // is a good idea, though odd aliasing should be extremely uncommon. - src, dst := src, dst if dst_len := len(dst); dst_len < len(src) { src = src[:dst_len] } + if bytes.alias_inexactly(dst, src) { + panic("crypto/chacha20: dst and src alias inexactly") + } + for remaining := len(src); remaining > 0; { // Process multiple blocks at once if ctx._off == _BLOCK_SIZE { diff --git a/core/crypto/crypto.odin b/core/crypto/crypto.odin index 05f25111a..323cc45d6 100644 --- a/core/crypto/crypto.odin +++ b/core/crypto/crypto.odin @@ -4,6 +4,7 @@ helper routines. */ package crypto +import "base:runtime" import "core:mem" // compare_constant_time returns 1 iff a and b are equal, 0 otherwise. @@ -49,6 +50,9 @@ compare_byte_ptrs_constant_time :: proc "contextless" (a, b: ^byte, n: int) -> i // the system entropy source. This routine will block if the system entropy // source is not ready yet. All system entropy source failures are treated // as catastrophic, resulting in a panic. +// +// Support for the system entropy source can be checked with the +// `HAS_RAND_BYTES` boolean constant. rand_bytes :: proc (dst: []byte) { // zero-fill the buffer first mem.zero_explicit(raw_data(dst), len(dst)) @@ -56,8 +60,27 @@ rand_bytes :: proc (dst: []byte) { _rand_bytes(dst) } -// has_rand_bytes returns true iff the target has support for accessing the -// system entropty source. -has_rand_bytes :: proc () -> bool { - return _has_rand_bytes() +// random_generator returns a `runtime.Random_Generator` backed by the +// system entropy source. +// +// Support for the system entropy source can be checked with the +// `HAS_RAND_BYTES` boolean constant. +random_generator :: proc() -> runtime.Random_Generator { + return { + procedure = proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) { + switch mode { + case .Read: + rand_bytes(p) + case .Reset: + // do nothing + case .Query_Info: + if len(p) != size_of(runtime.Random_Generator_Query_Info) { + return + } + info := (^runtime.Random_Generator_Query_Info)(raw_data(p)) + info^ += {.Uniform, .Cryptographic, .External_Entropy} + } + }, + data = nil, + } } diff --git a/core/crypto/kmac/kmac.odin b/core/crypto/kmac/kmac.odin index e5be6f91b..711f459b3 100644 --- a/core/crypto/kmac/kmac.odin +++ b/core/crypto/kmac/kmac.odin @@ -61,7 +61,7 @@ init_256 :: proc(ctx: ^Context, key, domain_sep: []byte) { update :: proc(ctx: ^Context, data: []byte) { assert(ctx.is_initialized) - shake.write(transmute(^shake.Context)(ctx), data) + shake.write((^shake.Context)(ctx), data) } // final finalizes the Context, writes the tag to dst, and calls reset @@ -75,7 +75,7 @@ final :: proc(ctx: ^Context, dst: []byte) { panic("crypto/kmac: invalid KMAC tag_size, too short") } - _sha3.final_cshake(transmute(^_sha3.Context)(ctx), dst) + _sha3.final_cshake((^_sha3.Context)(ctx), dst) } // clone clones the Context other into ctx. @@ -84,7 +84,7 @@ clone :: proc(ctx, other: ^Context) { return } - shake.clone(transmute(^shake.Context)(ctx), transmute(^shake.Context)(other)) + shake.clone((^shake.Context)(ctx), (^shake.Context)(other)) } // reset sanitizes the Context. The Context must be re-initialized to @@ -94,7 +94,7 @@ reset :: proc(ctx: ^Context) { return } - shake.reset(transmute(^shake.Context)(ctx)) + shake.reset((^shake.Context)(ctx)) } @(private) @@ -107,7 +107,7 @@ _init_kmac :: proc(ctx: ^Context, key, s: []byte, sec_strength: int) { panic("crypto/kmac: invalid KMAC key, too short") } - ctx_ := transmute(^_sha3.Context)(ctx) + ctx_ := (^_sha3.Context)(ctx) _sha3.init_cshake(ctx_, N_KMAC, s, sec_strength) _sha3.bytepad(ctx_, [][]byte{key}, _sha3.rate_cshake(sec_strength)) } diff --git a/core/crypto/legacy/keccak/keccak.odin b/core/crypto/legacy/keccak/keccak.odin index 7813a1ab4..6ca66b7ca 100644 --- a/core/crypto/legacy/keccak/keccak.odin +++ b/core/crypto/legacy/keccak/keccak.odin @@ -66,12 +66,12 @@ init_512 :: proc(ctx: ^Context) { @(private) _init :: proc(ctx: ^Context) { ctx.dsbyte = _sha3.DS_KECCAK - _sha3.init(transmute(^_sha3.Context)(ctx)) + _sha3.init((^_sha3.Context)(ctx)) } // update adds more data to the Context. update :: proc(ctx: ^Context, data: []byte) { - _sha3.update(transmute(^_sha3.Context)(ctx), data) + _sha3.update((^_sha3.Context)(ctx), data) } // final finalizes the Context, writes the digest to hash, and calls @@ -80,16 +80,16 @@ update :: proc(ctx: ^Context, data: []byte) { // Iff finalize_clone is set, final will work on a copy of the Context, // which is useful for for calculating rolling digests. final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { - _sha3.final(transmute(^_sha3.Context)(ctx), hash, finalize_clone) + _sha3.final((^_sha3.Context)(ctx), hash, finalize_clone) } // clone clones the Context other into ctx. clone :: proc(ctx, other: ^Context) { - _sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other)) + _sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other)) } // reset sanitizes the Context. The Context must be re-initialized to // be used again. reset :: proc(ctx: ^Context) { - _sha3.reset(transmute(^_sha3.Context)(ctx)) + _sha3.reset((^_sha3.Context)(ctx)) } diff --git a/core/crypto/rand_bsd.odin b/core/crypto/rand_bsd.odin index 7a0c42683..641b72933 100644 --- a/core/crypto/rand_bsd.odin +++ b/core/crypto/rand_bsd.odin @@ -1,16 +1,15 @@ -//+build freebsd, openbsd +//+build freebsd, openbsd, netbsd package crypto foreign import libc "system:c" +HAS_RAND_BYTES :: true + foreign libc { arc4random_buf :: proc(buf: [^]byte, nbytes: uint) --- } +@(private) _rand_bytes :: proc(dst: []byte) { arc4random_buf(raw_data(dst), len(dst)) } - -_has_rand_bytes :: proc() -> bool { - return true -} diff --git a/core/crypto/rand_darwin.odin b/core/crypto/rand_darwin.odin index c1a3d1dbc..df474bc4c 100644 --- a/core/crypto/rand_darwin.odin +++ b/core/crypto/rand_darwin.odin @@ -5,14 +5,13 @@ import "core:fmt" import CF "core:sys/darwin/CoreFoundation" import Sec "core:sys/darwin/Security" +HAS_RAND_BYTES :: true + +@(private) _rand_bytes :: proc(dst: []byte) { err := Sec.RandomCopyBytes(count=len(dst), bytes=raw_data(dst)) if err != .Success { - msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err)) - panic(fmt.tprintf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg)) + msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err)) + fmt.panicf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg) } } - -_has_rand_bytes :: proc() -> bool { - return true -} diff --git a/core/crypto/rand_generic.odin b/core/crypto/rand_generic.odin index cba49f700..46fb881b3 100644 --- a/core/crypto/rand_generic.odin +++ b/core/crypto/rand_generic.odin @@ -2,14 +2,14 @@ //+build !windows //+build !openbsd //+build !freebsd +//+build !netbsd //+build !darwin //+build !js package crypto +HAS_RAND_BYTES :: false + +@(private) _rand_bytes :: proc(dst: []byte) { unimplemented("crypto: rand_bytes not supported on this OS") } - -_has_rand_bytes :: proc() -> bool { - return false -} diff --git a/core/crypto/rand_js.odin b/core/crypto/rand_js.odin index 90f60b99b..72093810e 100644 --- a/core/crypto/rand_js.odin +++ b/core/crypto/rand_js.odin @@ -6,8 +6,12 @@ foreign odin_env { env_rand_bytes :: proc "contextless" (buf: []byte) --- } +HAS_RAND_BYTES :: true + +@(private) _MAX_PER_CALL_BYTES :: 65536 // 64kiB +@(private) _rand_bytes :: proc(dst: []byte) { dst := dst @@ -18,7 +22,3 @@ _rand_bytes :: proc(dst: []byte) { dst = dst[to_read:] } } - -_has_rand_bytes :: proc() -> bool { - return true -} diff --git a/core/crypto/rand_linux.odin b/core/crypto/rand_linux.odin index a9dc37415..7e0edbb7e 100644 --- a/core/crypto/rand_linux.odin +++ b/core/crypto/rand_linux.odin @@ -4,8 +4,12 @@ import "core:fmt" import "core:sys/linux" +HAS_RAND_BYTES :: true + +@(private) _MAX_PER_CALL_BYTES :: 33554431 // 2^25 - 1 +@(private) _rand_bytes :: proc (dst: []byte) { dst := dst l := len(dst) @@ -28,13 +32,9 @@ _rand_bytes :: proc (dst: []byte) { // All other failures are things that should NEVER happen // unless the kernel interface changes (ie: the Linux // developers break userland). - panic(fmt.tprintf("crypto: getrandom failed: %v", errno)) + fmt.panicf("crypto: getrandom failed: %v", errno) } l -= n_read dst = dst[n_read:] } } - -_has_rand_bytes :: proc() -> bool { - return true -} diff --git a/core/crypto/rand_windows.odin b/core/crypto/rand_windows.odin index 5cafe7fb5..9cd647cc1 100644 --- a/core/crypto/rand_windows.odin +++ b/core/crypto/rand_windows.odin @@ -4,24 +4,23 @@ import win32 "core:sys/windows" import "core:os" import "core:fmt" +HAS_RAND_BYTES :: true + +@(private) _rand_bytes :: proc(dst: []byte) { ret := (os.Errno)(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG)) if ret != os.ERROR_NONE { switch ret { - case os.ERROR_INVALID_HANDLE: - // The handle to the first parameter is invalid. - // This should not happen here, since we explicitly pass nil to it - panic("crypto: BCryptGenRandom Invalid handle for hAlgorithm") - case os.ERROR_INVALID_PARAMETER: - // One of the parameters was invalid - panic("crypto: BCryptGenRandom Invalid parameter") - case: - // Unknown error - panic(fmt.tprintf("crypto: BCryptGenRandom failed: %d\n", ret)) + case os.ERROR_INVALID_HANDLE: + // The handle to the first parameter is invalid. + // This should not happen here, since we explicitly pass nil to it + panic("crypto: BCryptGenRandom Invalid handle for hAlgorithm") + case os.ERROR_INVALID_PARAMETER: + // One of the parameters was invalid + panic("crypto: BCryptGenRandom Invalid parameter") + case: + // Unknown error + fmt.panicf("crypto: BCryptGenRandom failed: %d\n", ret) } } } - -_has_rand_bytes :: proc() -> bool { - return true -} diff --git a/core/crypto/ristretto255/ristretto255.odin b/core/crypto/ristretto255/ristretto255.odin index d1f2b6ee5..3a2307da0 100644 --- a/core/crypto/ristretto255/ristretto255.odin +++ b/core/crypto/ristretto255/ristretto255.odin @@ -112,7 +112,7 @@ ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool { return false } - b_ := transmute(^[32]byte)(raw_data(b)) + b_ := (^[32]byte)(raw_data(b)) s: field.Tight_Field_Element = --- defer field.fe_clear(&s) @@ -297,7 +297,7 @@ ge_bytes :: proc(ge: ^Group_Element, dst: []byte) { // 2. Return the 32-byte little-endian encoding of s. More // specifically, this is the encoding of the canonical // representation of s as an integer between 0 and p-1, inclusive. - dst_ := transmute(^[32]byte)(raw_data(dst)) + dst_ := (^[32]byte)(raw_data(dst)) field.fe_to_bytes(dst_, &tmp) field.fe_clear_vec([]^field.Tight_Field_Element{&u1, &u2, &tmp, &z_inv, &ix0, &iy0, &x, &y}) @@ -417,7 +417,7 @@ ge_is_identity :: proc(ge: ^Group_Element) -> int { @(private) ge_map :: proc "contextless" (ge: ^Group_Element, b: []byte) { - b_ := transmute(^[32]byte)(raw_data(b)) + b_ := (^[32]byte)(raw_data(b)) // The MAP function is defined on 32-byte strings as: // diff --git a/core/crypto/ristretto255/ristretto255_scalar.odin b/core/crypto/ristretto255/ristretto255_scalar.odin index f581e5963..1ecb490e0 100644 --- a/core/crypto/ristretto255/ristretto255_scalar.odin +++ b/core/crypto/ristretto255/ristretto255_scalar.odin @@ -46,7 +46,7 @@ sc_set_bytes_wide :: proc(sc: ^Scalar, b: []byte) { panic("crypto/ristretto255: invalid wide input size") } - b_ := transmute(^[WIDE_SCALAR_SIZE]byte)(raw_data(b)) + b_ := (^[WIDE_SCALAR_SIZE]byte)(raw_data(b)) grp.sc_set_bytes_wide(sc, b_) } diff --git a/core/crypto/sha3/sha3.odin b/core/crypto/sha3/sha3.odin index bc3e6e846..78057f1ca 100644 --- a/core/crypto/sha3/sha3.odin +++ b/core/crypto/sha3/sha3.odin @@ -68,12 +68,12 @@ init_512 :: proc(ctx: ^Context) { @(private) _init :: proc(ctx: ^Context) { ctx.dsbyte = _sha3.DS_SHA3 - _sha3.init(transmute(^_sha3.Context)(ctx)) + _sha3.init((^_sha3.Context)(ctx)) } // update adds more data to the Context. update :: proc(ctx: ^Context, data: []byte) { - _sha3.update(transmute(^_sha3.Context)(ctx), data) + _sha3.update((^_sha3.Context)(ctx), data) } // final finalizes the Context, writes the digest to hash, and calls @@ -82,16 +82,16 @@ update :: proc(ctx: ^Context, data: []byte) { // Iff finalize_clone is set, final will work on a copy of the Context, // which is useful for for calculating rolling digests. final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { - _sha3.final(transmute(^_sha3.Context)(ctx), hash, finalize_clone) + _sha3.final((^_sha3.Context)(ctx), hash, finalize_clone) } // clone clones the Context other into ctx. clone :: proc(ctx, other: ^Context) { - _sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other)) + _sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other)) } // reset sanitizes the Context. The Context must be re-initialized to // be used again. reset :: proc(ctx: ^Context) { - _sha3.reset(transmute(^_sha3.Context)(ctx)) + _sha3.reset((^_sha3.Context)(ctx)) } diff --git a/core/crypto/shake/shake.odin b/core/crypto/shake/shake.odin index 7da427485..4160f4cf9 100644 --- a/core/crypto/shake/shake.odin +++ b/core/crypto/shake/shake.odin @@ -24,35 +24,35 @@ Context :: distinct _sha3.Context // init_128 initializes a Context for SHAKE128. init_128 :: proc(ctx: ^Context) { - _sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, nil, 128) + _sha3.init_cshake((^_sha3.Context)(ctx), nil, nil, 128) } // init_256 initializes a Context for SHAKE256. init_256 :: proc(ctx: ^Context) { - _sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, nil, 256) + _sha3.init_cshake((^_sha3.Context)(ctx), nil, nil, 256) } // init_cshake_128 initializes a Context for cSHAKE128. init_cshake_128 :: proc(ctx: ^Context, domain_sep: []byte) { - _sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, domain_sep, 128) + _sha3.init_cshake((^_sha3.Context)(ctx), nil, domain_sep, 128) } // init_cshake_256 initializes a Context for cSHAKE256. init_cshake_256 :: proc(ctx: ^Context, domain_sep: []byte) { - _sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, domain_sep, 256) + _sha3.init_cshake((^_sha3.Context)(ctx), nil, domain_sep, 256) } // write writes more data into the SHAKE instance. This MUST not be called // after any reads have been done, and attempts to do so will panic. write :: proc(ctx: ^Context, data: []byte) { - _sha3.update(transmute(^_sha3.Context)(ctx), data) + _sha3.update((^_sha3.Context)(ctx), data) } // read reads output from the SHAKE instance. There is no practical upper // limit to the amount of data that can be read from SHAKE. After read has // been called one or more times, further calls to write will panic. read :: proc(ctx: ^Context, dst: []byte) { - ctx_ := transmute(^_sha3.Context)(ctx) + ctx_ := (^_sha3.Context)(ctx) if !ctx.is_finalized { _sha3.shake_xof(ctx_) } @@ -62,11 +62,11 @@ read :: proc(ctx: ^Context, dst: []byte) { // clone clones the Context other into ctx. clone :: proc(ctx, other: ^Context) { - _sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other)) + _sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other)) } // reset sanitizes the Context. The Context must be re-initialized to // be used again. reset :: proc(ctx: ^Context) { - _sha3.reset(transmute(^_sha3.Context)(ctx)) + _sha3.reset((^_sha3.Context)(ctx)) } diff --git a/core/crypto/tuplehash/tuplehash.odin b/core/crypto/tuplehash/tuplehash.odin index baba1ce59..ed0a3aa87 100644 --- a/core/crypto/tuplehash/tuplehash.odin +++ b/core/crypto/tuplehash/tuplehash.odin @@ -13,19 +13,19 @@ Context :: distinct _sha3.Context // init_128 initializes a Context for TupleHash128 or TupleHashXOF128. init_128 :: proc(ctx: ^Context, domain_sep: []byte) { - _sha3.init_cshake(transmute(^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 128) + _sha3.init_cshake((^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 128) } // init_256 initializes a Context for TupleHash256 or TupleHashXOF256. init_256 :: proc(ctx: ^Context, domain_sep: []byte) { - _sha3.init_cshake(transmute(^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 256) + _sha3.init_cshake((^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 256) } // write_element writes a tuple element into the TupleHash or TupleHashXOF // instance. This MUST not be called after any reads have been done, and // any attempts to do so will panic. write_element :: proc(ctx: ^Context, data: []byte) { - _, _ = _sha3.encode_string(transmute(^_sha3.Context)(ctx), data) + _, _ = _sha3.encode_string((^_sha3.Context)(ctx), data) } // final finalizes the Context, writes the digest to hash, and calls @@ -34,7 +34,7 @@ write_element :: proc(ctx: ^Context, data: []byte) { // Iff finalize_clone is set, final will work on a copy of the Context, // which is useful for for calculating rolling digests. final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { - _sha3.final_cshake(transmute(^_sha3.Context)(ctx), hash, finalize_clone) + _sha3.final_cshake((^_sha3.Context)(ctx), hash, finalize_clone) } // read reads output from the TupleHashXOF instance. There is no practical @@ -42,7 +42,7 @@ final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { // After read has been called one or more times, further calls to // write_element will panic. read :: proc(ctx: ^Context, dst: []byte) { - ctx_ := transmute(^_sha3.Context)(ctx) + ctx_ := (^_sha3.Context)(ctx) if !ctx.is_finalized { _sha3.encode_byte_len(ctx_, 0, false) // right_encode _sha3.shake_xof(ctx_) @@ -53,13 +53,13 @@ read :: proc(ctx: ^Context, dst: []byte) { // clone clones the Context other into ctx. clone :: proc(ctx, other: ^Context) { - _sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other)) + _sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other)) } // reset sanitizes the Context. The Context must be re-initialized to // be used again. reset :: proc(ctx: ^Context) { - _sha3.reset(transmute(^_sha3.Context)(ctx)) + _sha3.reset((^_sha3.Context)(ctx)) } @(private) diff --git a/core/debug/trace/doc.odin b/core/debug/trace/doc.odin new file mode 100644 index 000000000..e65548769 --- /dev/null +++ b/core/debug/trace/doc.odin @@ -0,0 +1,51 @@ +/* +A debug stack trace library. Only works when debug symbols are enabled `-debug`. + +Example: + import "base:runtime" + import "core:debug/trace" + + import "core:fmt" + + global_trace_ctx: trace.Context + + debug_trace_assertion_failure_proc :: proc(prefix, message: string, loc := #caller_location) -> ! { + runtime.print_caller_location(loc) + runtime.print_string(" ") + runtime.print_string(prefix) + if len(message) > 0 { + runtime.print_string(": ") + runtime.print_string(message) + } + runtime.print_byte('\n') + + ctx := &trace_ctx + if !trace.in_resolve(ctx) { + buf: [64]trace.Frame + runtime.print_string("Debug Trace:\n") + frames := trace.frames(ctx, 1, buf[:]) + for f, i in frames { + fl := trace.resolve(ctx, f, context.temp_allocator) + if fl.loc.file_path == "" && fl.loc.line == 0 { + continue + } + runtime.print_caller_location(fl.loc) + runtime.print_string(" - frame ") + runtime.print_int(i) + runtime.print_byte('\n') + } + } + runtime.trap() + } + + main :: proc() { + trace.init(&global_trace_ctx) + defer trace.destroy(&global_trace_ctx) + + context.assertion_failure_proc = debug_trace_assertion_failure_proc + + ... + } + +*/ +package debug_trace \ No newline at end of file diff --git a/core/debug/trace/trace.odin b/core/debug/trace/trace.odin new file mode 100644 index 000000000..134609b05 --- /dev/null +++ b/core/debug/trace/trace.odin @@ -0,0 +1,47 @@ +package debug_trace + +import "base:intrinsics" +import "base:runtime" + +Frame :: distinct uintptr + +Frame_Location :: struct { + using loc: runtime.Source_Code_Location, + allocator: runtime.Allocator, +} + +delete_frame_location :: proc(fl: Frame_Location) -> runtime.Allocator_Error { + allocator := fl.allocator + delete(fl.loc.procedure, allocator) or_return + delete(fl.loc.file_path, allocator) or_return + return nil +} + +Context :: struct { + in_resolve: bool, // atomic + impl: _Context, +} + +init :: proc(ctx: ^Context) -> bool { + return _init(ctx) +} + +destroy :: proc(ctx: ^Context) -> bool { + return _destroy(ctx) +} + +@(require_results) +frames :: proc(ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame { + return _frames(ctx, skip, frames_buffer) +} + +@(require_results) +resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: Frame_Location) { + return _resolve(ctx, frame, allocator) +} + + +@(require_results) +in_resolve :: proc "contextless" (ctx: ^Context) -> bool { + return intrinsics.atomic_load(&ctx.in_resolve) +} \ No newline at end of file diff --git a/core/debug/trace/trace_cpp.odin b/core/debug/trace/trace_cpp.odin new file mode 100644 index 000000000..dc723184a --- /dev/null +++ b/core/debug/trace/trace_cpp.odin @@ -0,0 +1,195 @@ +//+private file +//+build linux, darwin +package debug_trace + +import "base:intrinsics" +import "base:runtime" +import "core:strings" +import "core:fmt" +import "core:c" + +// NOTE: Relies on C++23 which adds and becomes ABI and that can be used +foreign import stdcpplibbacktrace "system:stdc++_libbacktrace" + +foreign import libdl "system:dl" + +backtrace_state :: struct {} +backtrace_error_callback :: proc "c" (data: rawptr, msg: cstring, errnum: c.int) +backtrace_simple_callback :: proc "c" (data: rawptr, pc: uintptr) -> c.int +backtrace_full_callback :: proc "c" (data: rawptr, pc: uintptr, filename: cstring, lineno: c.int, function: cstring) -> c.int +backtrace_syminfo_callback :: proc "c" (data: rawptr, pc: uintptr, symname: cstring, symval: uintptr, symsize: uintptr) + +@(default_calling_convention="c", link_prefix="__glibcxx_") +foreign stdcpplibbacktrace { + backtrace_create_state :: proc( + filename: cstring, + threaded: c.int, + error_callback: backtrace_error_callback, + data: rawptr, + ) -> ^backtrace_state --- + backtrace_simple :: proc( + state: ^backtrace_state, + skip: c.int, + callback: backtrace_simple_callback, + error_callback: backtrace_error_callback, + data: rawptr, + ) -> c.int --- + backtrace_pcinfo :: proc( + state: ^backtrace_state, + pc: uintptr, + callback: backtrace_full_callback, + error_callback: backtrace_error_callback, + data: rawptr, + ) -> c.int --- + backtrace_syminfo :: proc( + state: ^backtrace_state, + addr: uintptr, + callback: backtrace_syminfo_callback, + error_callback: backtrace_error_callback, + data: rawptr, + ) -> c.int --- + + // NOTE(bill): this is technically an internal procedure, but it is exposed + backtrace_free :: proc( + state: ^backtrace_state, + p: rawptr, + size: c.size_t, // unused + error_callback: backtrace_error_callback, // unused + data: rawptr, // unused + ) --- +} + +Dl_info :: struct { + dli_fname: cstring, + dli_fbase: rawptr, + dli_sname: cstring, + dli_saddr: rawptr, +} + +@(default_calling_convention="c") +foreign libdl { + dladdr :: proc(addr: rawptr, info: ^Dl_info) -> c.int --- +} + +@(private="package") +_Context :: struct { + state: ^backtrace_state, +} + +@(private="package") +_init :: proc(ctx: ^Context) -> (ok: bool) { + defer if !ok { destroy(ctx) } + + ctx.impl.state = backtrace_create_state("odin-debug-trace", 1, nil, ctx) + return ctx.impl.state != nil +} + +@(private="package") +_destroy :: proc(ctx: ^Context) -> bool { + if ctx != nil { + backtrace_free(ctx.impl.state, nil, 0, nil, nil) + } + return true +} + +@(private="package") +_frames :: proc "contextless" (ctx: ^Context, skip: uint, frames_buffer: []Frame) -> (frames: []Frame) { + Backtrace_Context :: struct { + ctx: ^Context, + frames: []Frame, + frame_count: int, + } + + btc := &Backtrace_Context{ + ctx = ctx, + frames = frames_buffer, + } + backtrace_simple( + ctx.impl.state, + c.int(skip + 2), + proc "c" (user: rawptr, address: uintptr) -> c.int { + btc := (^Backtrace_Context)(user) + address := Frame(address) + if address == 0 { + return 1 + } + if btc.frame_count == len(btc.frames) { + return 1 + } + btc.frames[btc.frame_count] = address + btc.frame_count += 1 + return 0 + }, + nil, + btc, + ) + + if btc.frame_count > 0 { + frames = btc.frames[:btc.frame_count] + } + return +} + +@(private="package") +_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> Frame_Location { + intrinsics.atomic_store(&ctx.in_resolve, true) + defer intrinsics.atomic_store(&ctx.in_resolve, false) + + Backtrace_Context :: struct { + rt_ctx: runtime.Context, + allocator: runtime.Allocator, + frame: Frame_Location, + } + + btc := &Backtrace_Context{ + rt_ctx = context, + allocator = allocator, + } + done := backtrace_pcinfo( + ctx.impl.state, + uintptr(frame), + proc "c" (data: rawptr, address: uintptr, file: cstring, line: c.int, symbol: cstring) -> c.int { + btc := (^Backtrace_Context)(data) + context = btc.rt_ctx + + frame := &btc.frame + + if file != nil { + frame.file_path = strings.clone_from_cstring(file, btc.allocator) + } else if info: Dl_info; dladdr(rawptr(address), &info) != 0 && info.dli_fname != "" { + frame.file_path = strings.clone_from_cstring(info.dli_fname, btc.allocator) + } + if symbol != nil { + frame.procedure = strings.clone_from_cstring(symbol, btc.allocator) + } else if info: Dl_info; dladdr(rawptr(address), &info) != 0 && info.dli_sname != "" { + frame.procedure = strings.clone_from_cstring(info.dli_sname, btc.allocator) + } else { + frame.procedure = fmt.aprintf("(procedure: 0x%x)", allocator=btc.allocator) + } + frame.line = i32(line) + return 0 + }, + nil, + btc, + ) + if done != 0 { + return btc.frame + } + + // NOTE(bill): pcinfo cannot resolve, but it might be possible to get the procedure name at least + backtrace_syminfo( + ctx.impl.state, + uintptr(frame), + proc "c" (data: rawptr, address: uintptr, symbol: cstring, _ignore0, _ignore1: uintptr) { + if symbol != nil { + btc := (^Backtrace_Context)(data) + context = btc.rt_ctx + btc.frame.procedure = strings.clone_from_cstring(symbol, btc.allocator) + } + }, + nil, + btc, + ) + + return btc.frame +} \ No newline at end of file diff --git a/core/debug/trace/trace_nil.odin b/core/debug/trace/trace_nil.odin new file mode 100644 index 000000000..8611d7726 --- /dev/null +++ b/core/debug/trace/trace_nil.odin @@ -0,0 +1,20 @@ +//+build !windows !linux !darwin +package debug_trace + +import "base:runtime" + +_Context :: struct { +} + +_init :: proc(ctx: ^Context) -> (ok: bool) { + return true +} +_destroy :: proc(ctx: ^Context) -> bool { + return true +} +_frames :: proc(ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame { + return nil +} +_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: Frame_Location) { + return +} diff --git a/core/debug/trace/trace_windows.odin b/core/debug/trace/trace_windows.odin new file mode 100644 index 000000000..de1461e96 --- /dev/null +++ b/core/debug/trace/trace_windows.odin @@ -0,0 +1,68 @@ +//+private +//+build windows +package debug_trace + +import "base:intrinsics" +import "base:runtime" + +import win32 "core:sys/windows" +import "core:fmt" + +_Context :: struct { + hProcess: win32.HANDLE, + lock: win32.SRWLOCK, +} + +_init :: proc "contextless" (ctx: ^Context) -> (ok: bool) { + defer if !ok { _destroy(ctx) } + ctx.impl.hProcess = win32.GetCurrentProcess() + win32.SymInitialize(ctx.impl.hProcess, nil, true) or_return + win32.SymSetOptions(win32.SYMOPT_LOAD_LINES) + return true +} + +_destroy :: proc "contextless" (ctx: ^Context) -> bool { + if ctx != nil { + win32.SymCleanup(ctx.impl.hProcess) + } + return true +} + +_frames :: proc "contextless" (ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame { + frame_count := win32.RtlCaptureStackBackTrace(u32(skip) + 2, u32(len(frames_buffer)), ([^]rawptr)(&frames_buffer[0]), nil) + for i in 0.. (fl: Frame_Location) { + intrinsics.atomic_store(&ctx.in_resolve, true) + defer intrinsics.atomic_store(&ctx.in_resolve, false) + + // NOTE(bill): Dbghelp is not thread-safe + win32.AcquireSRWLockExclusive(&ctx.impl.lock) + defer win32.ReleaseSRWLockExclusive(&ctx.impl.lock) + + data: [size_of(win32.SYMBOL_INFOW) + size_of([256]win32.WCHAR)]byte + symbol := (^win32.SYMBOL_INFOW)(&data[0]) + symbol.SizeOfStruct = size_of(symbol) + symbol.MaxNameLen = 255 + if win32.SymFromAddrW(ctx.impl.hProcess, win32.DWORD64(frame), &{}, symbol) { + fl.procedure, _ = win32.wstring_to_utf8(&symbol.Name[0], -1, allocator) + } else { + fl.procedure = fmt.aprintf("(procedure: 0x%x)", frame, allocator=allocator) + } + + line: win32.IMAGEHLP_LINE64 + line.SizeOfStruct = size_of(line) + if win32.SymGetLineFromAddrW64(ctx.impl.hProcess, win32.DWORD64(frame), &{}, &line) { + fl.file_path, _ = win32.wstring_to_utf8(line.FileName, -1, allocator) + fl.line = i32(line.LineNumber) + } + + return +} \ No newline at end of file diff --git a/core/dynlib/lib.odin b/core/dynlib/lib.odin index 3d41cbe2e..09e16002d 100644 --- a/core/dynlib/lib.odin +++ b/core/dynlib/lib.odin @@ -16,15 +16,12 @@ Library :: distinct rawptr Loads a dynamic library from the filesystem. The paramater `global_symbols` makes the symbols in the loaded library available to resolve references in subsequently loaded libraries. -The paramater `global_symbols` is only used for the platforms `linux`, `darwin`, `freebsd` and `openbsd`. +The parameter `global_symbols` is only used for the platforms `linux`, `darwin`, `freebsd` and `openbsd`. On `windows` this paramater is ignored. The underlying behaviour is platform specific. On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlopen`. -On `windows` refer to `LoadLibraryW`. - -**Implicit Allocators** -`context.temp_allocator` +On `windows` refer to `LoadLibraryW`. Also temporarily needs an allocator to convert a string. Example: import "core:dynlib" @@ -79,10 +76,7 @@ Loads the address of a procedure/variable from a dynamic library. The underlying behaviour is platform specific. On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlsym`. -On `windows` refer to `GetProcAddress`. - -**Implicit Allocators** -`context.temp_allocator` +On `windows` refer to `GetProcAddress`. Also temporarily needs an allocator to convert a string. Example: import "core:dynlib" @@ -177,9 +171,7 @@ initialize_symbols :: proc( return count, count > 0 } -/* -Returns an error message for the last failed procedure call. -*/ +// Returns an error message for the last failed procedure call. last_error :: proc() -> string { return _last_error() -} +} \ No newline at end of file diff --git a/core/dynlib/lib_js.odin b/core/dynlib/lib_js.odin index 866874ee8..bfc724c12 100644 --- a/core/dynlib/lib_js.odin +++ b/core/dynlib/lib_js.odin @@ -16,4 +16,4 @@ _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found _last_error :: proc() -> string { return "" -} +} \ No newline at end of file diff --git a/core/dynlib/lib_unix.odin b/core/dynlib/lib_unix.odin index 97f70b576..8adaadb2d 100644 --- a/core/dynlib/lib_unix.odin +++ b/core/dynlib/lib_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd +//+build linux, darwin, freebsd, openbsd, netbsd //+private package dynlib @@ -26,4 +26,4 @@ _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found _last_error :: proc() -> string { err := os.dlerror() return "unknown" if err == "" else err -} +} \ No newline at end of file diff --git a/core/dynlib/lib_windows.odin b/core/dynlib/lib_windows.odin index c7bfe1537..b41abe3b2 100644 --- a/core/dynlib/lib_windows.odin +++ b/core/dynlib/lib_windows.odin @@ -4,14 +4,12 @@ package dynlib import win32 "core:sys/windows" import "core:strings" -import "base:runtime" import "core:reflect" -_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { +_load_library :: proc(path: string, global_symbols := false, allocator := context.temp_allocator) -> (Library, bool) { // NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL - - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wide_path := win32.utf8_to_wstring(path, context.temp_allocator) + wide_path := win32.utf8_to_wstring(path, allocator) + defer free(wide_path, allocator) handle := cast(Library)win32.LoadLibraryW(wide_path) return handle, handle != nil } @@ -21,9 +19,9 @@ _unload_library :: proc(library: Library) -> bool { return bool(ok) } -_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - c_str := strings.clone_to_cstring(symbol, context.temp_allocator) +_symbol_address :: proc(library: Library, symbol: string, allocator := context.temp_allocator) -> (ptr: rawptr, found: bool) { + c_str := strings.clone_to_cstring(symbol, allocator) + defer delete(c_str, allocator) ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str) found = ptr != nil return @@ -33,4 +31,4 @@ _last_error :: proc() -> string { err := win32.System_Error(win32.GetLastError()) err_msg := reflect.enum_string(err) return "unknown" if err_msg == "" else err_msg -} +} \ No newline at end of file diff --git a/core/encoding/ansi/ansi.odin b/core/encoding/ansi/ansi.odin new file mode 100644 index 000000000..5550a1671 --- /dev/null +++ b/core/encoding/ansi/ansi.odin @@ -0,0 +1,137 @@ +package ansi + +BEL :: "\a" // Bell +BS :: "\b" // Backspace +ESC :: "\e" // Escape + +// Fe Escape sequences + +CSI :: ESC + "[" // Control Sequence Introducer +OSC :: ESC + "]" // Operating System Command +ST :: ESC + "\\" // String Terminator + +// CSI sequences + +CUU :: "A" // Cursor Up +CUD :: "B" // Cursor Down +CUF :: "C" // Cursor Forward +CUB :: "D" // Cursor Back +CNL :: "E" // Cursor Next Line +CPL :: "F" // Cursor Previous Line +CHA :: "G" // Cursor Horizontal Absolute +CUP :: "H" // Cursor Position +ED :: "J" // Erase in Display +EL :: "K" // Erase in Line +SU :: "S" // Scroll Up +SD :: "T" // Scroll Down +HVP :: "f" // Horizontal Vertical Position +SGR :: "m" // Select Graphic Rendition +AUX_ON :: "5i" // AUX Port On +AUX_OFF :: "4i" // AUX Port Off +DSR :: "6n" // Device Status Report + +// CSI: private sequences + +SCP :: "s" // Save Current Cursor Position +RCP :: "u" // Restore Saved Cursor Position +DECAWM_ON :: "?7h" // Auto Wrap Mode (Enabled) +DECAWM_OFF :: "?7l" // Auto Wrap Mode (Disabled) +DECTCEM_SHOW :: "?25h" // Text Cursor Enable Mode (Visible) +DECTCEM_HIDE :: "?25l" // Text Cursor Enable Mode (Invisible) + +// SGR sequences + +RESET :: "0" +BOLD :: "1" +FAINT :: "2" +ITALIC :: "3" // Not widely supported. +UNDERLINE :: "4" +BLINK_SLOW :: "5" +BLINK_RAPID :: "6" // Not widely supported. +INVERT :: "7" // Also known as reverse video. +HIDE :: "8" // Not widely supported. +STRIKE :: "9" +FONT_PRIMARY :: "10" +FONT_ALT1 :: "11" +FONT_ALT2 :: "12" +FONT_ALT3 :: "13" +FONT_ALT4 :: "14" +FONT_ALT5 :: "15" +FONT_ALT6 :: "16" +FONT_ALT7 :: "17" +FONT_ALT8 :: "18" +FONT_ALT9 :: "19" +FONT_FRAKTUR :: "20" // Rarely supported. +UNDERLINE_DOUBLE :: "21" // May be interpreted as "disable bold." +NO_BOLD_FAINT :: "22" +NO_ITALIC_BLACKLETTER :: "23" +NO_UNDERLINE :: "24" +NO_BLINK :: "25" +PROPORTIONAL_SPACING :: "26" +NO_REVERSE :: "27" +NO_HIDE :: "28" +NO_STRIKE :: "29" + +FG_BLACK :: "30" +FG_RED :: "31" +FG_GREEN :: "32" +FG_YELLOW :: "33" +FG_BLUE :: "34" +FG_MAGENTA :: "35" +FG_CYAN :: "36" +FG_WHITE :: "37" +FG_COLOR :: "38" +FG_COLOR_8_BIT :: "38;5" // Followed by ";n" where n is in 0..=255 +FG_COLOR_24_BIT :: "38;2" // Followed by ";r;g;b" where r,g,b are in 0..=255 +FG_DEFAULT :: "39" + +BG_BLACK :: "40" +BG_RED :: "41" +BG_GREEN :: "42" +BG_YELLOW :: "43" +BG_BLUE :: "44" +BG_MAGENTA :: "45" +BG_CYAN :: "46" +BG_WHITE :: "47" +BG_COLOR :: "48" +BG_COLOR_8_BIT :: "48;5" // Followed by ";n" where n is in 0..=255 +BG_COLOR_24_BIT :: "48;2" // Followed by ";r;g;b" where r,g,b are in 0..=255 +BG_DEFAULT :: "49" + +NO_PROPORTIONAL_SPACING :: "50" +FRAMED :: "51" +ENCIRCLED :: "52" +OVERLINED :: "53" +NO_FRAME_ENCIRCLE :: "54" +NO_OVERLINE :: "55" + +// SGR: non-standard bright colors + +FG_BRIGHT_BLACK :: "90" // Also known as grey. +FG_BRIGHT_RED :: "91" +FG_BRIGHT_GREEN :: "92" +FG_BRIGHT_YELLOW :: "93" +FG_BRIGHT_BLUE :: "94" +FG_BRIGHT_MAGENTA :: "95" +FG_BRIGHT_CYAN :: "96" +FG_BRIGHT_WHITE :: "97" + +BG_BRIGHT_BLACK :: "100" // Also known as grey. +BG_BRIGHT_RED :: "101" +BG_BRIGHT_GREEN :: "102" +BG_BRIGHT_YELLOW :: "103" +BG_BRIGHT_BLUE :: "104" +BG_BRIGHT_MAGENTA :: "105" +BG_BRIGHT_CYAN :: "106" +BG_BRIGHT_WHITE :: "107" + +// Fp Escape sequences + +DECSC :: ESC + "7" // DEC Save Cursor +DECRC :: ESC + "8" // DEC Restore Cursor + +// OSC sequences + +WINDOW_TITLE :: "2" // Followed by ";" ST. +HYPERLINK :: "8" // Followed by ";[params];" ST. Closed by OSC HYPERLINK ";;" ST. +CLIPBOARD :: "52" // Followed by ";c;" ST. diff --git a/core/encoding/ansi/doc.odin b/core/encoding/ansi/doc.odin new file mode 100644 index 000000000..a0945c581 --- /dev/null +++ b/core/encoding/ansi/doc.odin @@ -0,0 +1,20 @@ +/* +package ansi implements constant references to many widely-supported ANSI +escape codes, primarily used in terminal emulators for enhanced graphics, such +as colors, text styling, and animated displays. + +For example, you can print out a line of cyan text like this: + fmt.println(ansi.CSI + ansi.FG_CYAN + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR) + +Multiple SGR (Select Graphic Rendition) codes can be joined by semicolons: + fmt.println(ansi.CSI + ansi.BOLD + ";" + ansi.FG_BLUE + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR) + +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 +*/ +package ansi diff --git a/core/encoding/base32/base32.odin b/core/encoding/base32/base32.odin index 962a3ead4..f3320428d 100644 --- a/core/encoding/base32/base32.odin +++ b/core/encoding/base32/base32.odin @@ -8,141 +8,141 @@ package encoding_base32 // truncate it from the encoded output. ENC_TABLE := [32]byte { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', '2', '3', '4', '5', '6', '7', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', '2', '3', '4', '5', '6', '7', } PADDING :: '=' DEC_TABLE := [?]u8 { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 26, 27, 28, 29, 30, 31, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 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, 0, 0, 0, 0, 0, - 0, 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, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 26, 27, 28, 29, 30, 31, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 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, 0, 0, 0, 0, 0, + 0, 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, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> string { - out_length := (len(data) + 4) / 5 * 8 - out := make([]byte, out_length) - _encode(out, data) - return string(out) + out_length := (len(data) + 4) / 5 * 8 + out := make([]byte, out_length) + _encode(out, data) + return string(out) } @private _encode :: proc(out, data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) { - out := out - data := data + out := out + data := data - for len(data) > 0 { - carry: byte - switch len(data) { - case: - out[7] = ENC_TABLE[data[4] & 0x1f] - carry = data[4] >> 5 - fallthrough - case 4: - out[6] = ENC_TABLE[carry | (data[3] << 3) & 0x1f] - out[5] = ENC_TABLE[(data[3] >> 2) & 0x1f] - carry = data[3] >> 7 - fallthrough - case 3: - out[4] = ENC_TABLE[carry | (data[2] << 1) & 0x1f] - carry = (data[2] >> 4) & 0x1f - fallthrough - case 2: - out[3] = ENC_TABLE[carry | (data[1] << 4) & 0x1f] - out[2] = ENC_TABLE[(data[1] >> 1) & 0x1f] - carry = (data[1] >> 6) & 0x1f - fallthrough - case 1: - out[1] = ENC_TABLE[carry | (data[0] << 2) & 0x1f] - out[0] = ENC_TABLE[data[0] >> 3] - } + for len(data) > 0 { + carry: byte + switch len(data) { + case: + out[7] = ENC_TABLE[data[4] & 0x1f] + carry = data[4] >> 5 + fallthrough + case 4: + out[6] = ENC_TABLE[carry | (data[3] << 3) & 0x1f] + out[5] = ENC_TABLE[(data[3] >> 2) & 0x1f] + carry = data[3] >> 7 + fallthrough + case 3: + out[4] = ENC_TABLE[carry | (data[2] << 1) & 0x1f] + carry = (data[2] >> 4) & 0x1f + fallthrough + case 2: + out[3] = ENC_TABLE[carry | (data[1] << 4) & 0x1f] + out[2] = ENC_TABLE[(data[1] >> 1) & 0x1f] + carry = (data[1] >> 6) & 0x1f + fallthrough + case 1: + out[1] = ENC_TABLE[carry | (data[0] << 2) & 0x1f] + out[0] = ENC_TABLE[data[0] >> 3] + } - if len(data) < 5 { - out[7] = byte(PADDING) - if len(data) < 4 { - out[6] = byte(PADDING) - out[5] = byte(PADDING) - if len(data) < 3 { - out[4] = byte(PADDING) - if len(data) < 2 { - out[3] = byte(PADDING) - out[2] = byte(PADDING) - } - } - } - break - } - data = data[5:] - out = out[8:] - } + if len(data) < 5 { + out[7] = byte(PADDING) + if len(data) < 4 { + out[6] = byte(PADDING) + out[5] = byte(PADDING) + if len(data) < 3 { + out[4] = byte(PADDING) + if len(data) < 2 { + out[3] = byte(PADDING) + out[2] = byte(PADDING) + } + } + } + break + } + data = data[5:] + out = out[8:] + } } decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> []byte #no_bounds_check{ - if len(data) == 0 { - return nil - } + if len(data) == 0 { + return nil + } - outi := 0 - data := data + outi := 0 + data := data - out := make([]byte, len(data) / 8 * 5, allocator) - end := false - for len(data) > 0 && !end { - dbuf : [8]byte - dlen := 8 + out := make([]byte, len(data) / 8 * 5, allocator) + end := false + for len(data) > 0 && !end { + dbuf : [8]byte + dlen := 8 - for j := 0; j < 8; { - if len(data) == 0 { - dlen, end = j, true - break - } - input := data[0] - data = data[1:] - if input == byte(PADDING) && j >= 2 && len(data) < 8 { - assert(!(len(data) + j < 8 - 1), "Corrupted input") - for k := 0; k < 8-1-j; k +=1 { - assert(len(data) < k || data[k] == byte(PADDING), "Corrupted input") - } - dlen, end = j, true - assert(dlen != 1 && dlen != 3 && dlen != 6, "Corrupted input") - break - } - dbuf[j] = DEC_TABLE[input] - assert(dbuf[j] != 0xff, "Corrupted input") - j += 1 - } + for j := 0; j < 8; { + if len(data) == 0 { + dlen, end = j, true + break + } + input := data[0] + data = data[1:] + if input == byte(PADDING) && j >= 2 && len(data) < 8 { + assert(!(len(data) + j < 8 - 1), "Corrupted input") + for k := 0; k < 8-1-j; k +=1 { + assert(len(data) < k || data[k] == byte(PADDING), "Corrupted input") + } + dlen, end = j, true + assert(dlen != 1 && dlen != 3 && dlen != 6, "Corrupted input") + break + } + dbuf[j] = DEC_TABLE[input] + assert(dbuf[j] != 0xff, "Corrupted input") + j += 1 + } - switch dlen { - case 8: - out[outi + 4] = dbuf[6] << 5 | dbuf[7] - fallthrough - case 7: - out[outi + 3] = dbuf[4] << 7 | dbuf[5] << 2 | dbuf[6] >> 3 - fallthrough - case 5: - out[outi + 2] = dbuf[3] << 4 | dbuf[4] >> 1 - fallthrough - case 4: - out[outi + 1] = dbuf[1] << 6 | dbuf[2] << 1 | dbuf[3] >> 4 - fallthrough - case 2: - out[outi + 0] = dbuf[0] << 3 | dbuf[1] >> 2 - } - outi += 5 - } - return out + switch dlen { + case 8: + out[outi + 4] = dbuf[6] << 5 | dbuf[7] + fallthrough + case 7: + out[outi + 3] = dbuf[4] << 7 | dbuf[5] << 2 | dbuf[6] >> 3 + fallthrough + case 5: + out[outi + 2] = dbuf[3] << 4 | dbuf[4] >> 1 + fallthrough + case 4: + out[outi + 1] = dbuf[1] << 6 | dbuf[2] << 1 | dbuf[3] >> 4 + fallthrough + case 2: + out[outi + 0] = dbuf[0] << 3 | dbuf[1] >> 2 + } + outi += 5 + } + return out } diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index d0e406ab1..7897b2a37 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -320,8 +320,8 @@ to_diagnostic_format :: proc { // Turns the given CBOR value into a human-readable string. // See docs on the proc group `diagnose` for more info. -to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator) -> (string, mem.Allocator_Error) #optional_allocator_error { - b := strings.builder_make(allocator) +to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator, loc := #caller_location) -> (string, mem.Allocator_Error) #optional_allocator_error { + b := strings.builder_make(allocator, loc) w := strings.to_stream(&b) err := to_diagnostic_format_writer(w, val, padding) if err == .EOF { diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index 0d276a7a1..bfeb147c4 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -95,24 +95,25 @@ decode :: decode_from // Decodes the given string as CBOR. // See docs on the proc group `decode` for more information. -decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) { +decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { r: strings.Reader strings.reader_init(&r, s) - return decode_from_reader(strings.reader_to_stream(&r), flags, allocator) + return decode_from_reader(strings.reader_to_stream(&r), flags, allocator, loc) } // Reads a CBOR value from the given reader. // See docs on the proc group `decode` for more information. -decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) { +decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { return decode_from_decoder( Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, allocator=allocator, + loc = loc, ) } // Reads a CBOR value from the given decoder. // See docs on the proc group `decode` for more information. -decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: Value, err: Decode_Error) { +decode_from_decoder :: proc(d: Decoder, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { context.allocator = allocator d := d @@ -121,13 +122,13 @@ decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: V d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC } - v, err = _decode_from_decoder(d) + v, err = _decode_from_decoder(d, {}, allocator, loc) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } return } -_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value, err: Decode_Error) { +_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0), allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { hdr := hdr r := d.reader if hdr == Header(0) { hdr = _decode_header(r) or_return } @@ -161,11 +162,11 @@ _decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value, switch maj { case .Unsigned: return _decode_tiny_u8(add) case .Negative: return Negative_U8(_decode_tiny_u8(add) or_return), nil - case .Bytes: return _decode_bytes_ptr(d, add) - case .Text: return _decode_text_ptr(d, add) - case .Array: return _decode_array_ptr(d, add) - case .Map: return _decode_map_ptr(d, add) - case .Tag: return _decode_tag_ptr(d, add) + case .Bytes: return _decode_bytes_ptr(d, add, .Bytes, allocator, loc) + case .Text: return _decode_text_ptr(d, add, allocator, loc) + case .Array: return _decode_array_ptr(d, add, allocator, loc) + case .Map: return _decode_map_ptr(d, add, allocator, loc) + case .Tag: return _decode_tag_ptr(d, add, allocator, loc) case .Other: return _decode_tiny_simple(add) case: return nil, .Bad_Major } @@ -203,27 +204,27 @@ encode :: encode_into // Encodes the CBOR value into binary CBOR allocated on the given allocator. // See the docs on the proc group `encode_into` for more info. -encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (data: []byte, err: Encode_Error) { - b := strings.builder_make(allocator) or_return +encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (data: []byte, err: Encode_Error) { + b := strings.builder_make(allocator, loc) or_return encode_into_builder(&b, v, flags, temp_allocator) or_return return b.buf[:], nil } // Encodes the CBOR value into binary CBOR written to the given builder. // See the docs on the proc group `encode_into` for more info. -encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error { - return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator) +encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Encode_Error { + return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator, loc=loc) } // Encodes the CBOR value into binary CBOR written to the given writer. // See the docs on the proc group `encode_into` for more info. -encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error { - return encode_into_encoder(Encoder{flags, w, temp_allocator}, v) +encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Encode_Error { + return encode_into_encoder(Encoder{flags, w, temp_allocator}, v, loc=loc) } // Encodes the CBOR value into binary CBOR written to the given encoder. // See the docs on the proc group `encode_into` for more info. -encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error { +encode_into_encoder :: proc(e: Encoder, v: Value, loc := #caller_location) -> Encode_Error { e := e if e.temp_allocator.procedure == nil { @@ -232,7 +233,7 @@ encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error { if .Self_Described_CBOR in e.flags { _encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag) or_return - e.flags &~= { .Self_Described_CBOR } + e.flags -= { .Self_Described_CBOR } } switch v_spec in v { @@ -366,21 +367,21 @@ _encode_u64_exact :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> (er return } -_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes) -> (v: ^Bytes, err: Decode_Error) { - v = new(Bytes) or_return - defer if err != nil { free(v) } +_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator, loc := #caller_location) -> (v: ^Bytes, err: Decode_Error) { + v = new(Bytes, allocator, loc) or_return + defer if err != nil { free(v, allocator, loc) } - v^ = _decode_bytes(d, add, type) or_return + v^ = _decode_bytes(d, add, type, allocator, loc) or_return return } -_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator) -> (v: Bytes, err: Decode_Error) { +_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator, loc := #caller_location) -> (v: Bytes, err: Decode_Error) { context.allocator = allocator add := add n, scap := _decode_len_str(d, add) or_return - buf := strings.builder_make(0, scap) or_return + buf := strings.builder_make(0, scap, allocator, loc) or_return defer if err != nil { strings.builder_destroy(&buf) } buf_stream := strings.to_stream(&buf) @@ -422,44 +423,44 @@ _decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := c _encode_bytes :: proc(e: Encoder, val: Bytes, major: Major = .Bytes) -> (err: Encode_Error) { assert(len(val) >= 0) _encode_u64(e, u64(len(val)), major) or_return - _, err = io.write_full(e.writer, val[:]) + _, err = io.write_full(e.writer, val[:]) return } -_decode_text_ptr :: proc(d: Decoder, add: Add) -> (v: ^Text, err: Decode_Error) { - v = new(Text) or_return +_decode_text_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Text, err: Decode_Error) { + v = new(Text, allocator, loc) or_return defer if err != nil { free(v) } - v^ = _decode_text(d, add) or_return + v^ = _decode_text(d, add, allocator, loc) or_return return } -_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator) -> (v: Text, err: Decode_Error) { - return (Text)(_decode_bytes(d, add, .Text, allocator) or_return), nil +_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Text, err: Decode_Error) { + return (Text)(_decode_bytes(d, add, .Text, allocator, loc) or_return), nil } _encode_text :: proc(e: Encoder, val: Text) -> Encode_Error { - return _encode_bytes(e, transmute([]byte)val, .Text) + return _encode_bytes(e, transmute([]byte)val, .Text) } -_decode_array_ptr :: proc(d: Decoder, add: Add) -> (v: ^Array, err: Decode_Error) { - v = new(Array) or_return +_decode_array_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Array, err: Decode_Error) { + v = new(Array, allocator, loc) or_return defer if err != nil { free(v) } - v^ = _decode_array(d, add) or_return + v^ = _decode_array(d, add, allocator, loc) or_return return } -_decode_array :: proc(d: Decoder, add: Add) -> (v: Array, err: Decode_Error) { +_decode_array :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Array, err: Decode_Error) { n, scap := _decode_len_container(d, add) or_return - array := make([dynamic]Value, 0, scap) or_return + array := make([dynamic]Value, 0, scap, allocator, loc) or_return defer if err != nil { - for entry in array { destroy(entry) } - delete(array) + for entry in array { destroy(entry, allocator) } + delete(array, loc) } for i := 0; n == -1 || i < n; i += 1 { - val, verr := _decode_from_decoder(d) + val, verr := _decode_from_decoder(d, {}, allocator, loc) if n == -1 && verr == .Break { break } else if verr != nil { @@ -479,45 +480,45 @@ _decode_array :: proc(d: Decoder, add: Add) -> (v: Array, err: Decode_Error) { _encode_array :: proc(e: Encoder, arr: Array) -> Encode_Error { assert(len(arr) >= 0) _encode_u64(e, u64(len(arr)), .Array) - for val in arr { - encode(e, val) or_return - } - return nil + for val in arr { + encode(e, val) or_return + } + return nil } -_decode_map_ptr :: proc(d: Decoder, add: Add) -> (v: ^Map, err: Decode_Error) { - v = new(Map) or_return +_decode_map_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Map, err: Decode_Error) { + v = new(Map, allocator, loc) or_return defer if err != nil { free(v) } - v^ = _decode_map(d, add) or_return + v^ = _decode_map(d, add, allocator, loc) or_return return } -_decode_map :: proc(d: Decoder, add: Add) -> (v: Map, err: Decode_Error) { +_decode_map :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Map, err: Decode_Error) { n, scap := _decode_len_container(d, add) or_return - items := make([dynamic]Map_Entry, 0, scap) or_return + items := make([dynamic]Map_Entry, 0, scap, allocator, loc) or_return defer if err != nil { for entry in items { destroy(entry.key) destroy(entry.value) } - delete(items) + delete(items, loc) } for i := 0; n == -1 || i < n; i += 1 { - key, kerr := _decode_from_decoder(d) + key, kerr := _decode_from_decoder(d, {}, allocator, loc) if n == -1 && kerr == .Break { break } else if kerr != nil { return nil, kerr } - value := _decode_from_decoder(d) or_return + value := _decode_from_decoder(d, {}, allocator, loc) or_return append(&items, Map_Entry{ key = key, value = value, - }) or_return + }, loc) or_return } if .Shrink_Excess in d.flags { shrink(&items) } @@ -575,23 +576,23 @@ _encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) { encode(e, entry.entry.value) or_return } - return nil + return nil } -_decode_tag_ptr :: proc(d: Decoder, add: Add) -> (v: Value, err: Decode_Error) { - tag := _decode_tag(d, add) or_return +_decode_tag_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { + tag := _decode_tag(d, add, allocator, loc) or_return if t, ok := tag.?; ok { defer if err != nil { destroy(t.value) } - tp := new(Tag) or_return + tp := new(Tag, allocator, loc) or_return tp^ = t return tp, nil } // no error, no tag, this was the self described CBOR tag, skip it. - return _decode_from_decoder(d) + return _decode_from_decoder(d, {}, allocator, loc) } -_decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error) { +_decode_tag :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Maybe(Tag), err: Decode_Error) { num := _decode_uint_as_u64(d.reader, add) or_return // CBOR can be wrapped in a tag that decoders can use to see/check if the binary data is CBOR. @@ -602,7 +603,7 @@ _decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error) t := Tag{ number = num, - value = _decode_from_decoder(d) or_return, + value = _decode_from_decoder(d, {}, allocator, loc) or_return, } if nested, ok := t.value.(^Tag); ok { @@ -625,7 +626,7 @@ _decode_uint_as_u64 :: proc(r: io.Reader, add: Add) -> (nr: u64, err: Decode_Err _encode_tag :: proc(e: Encoder, val: Tag) -> Encode_Error { _encode_u64(e, val.number, .Tag) or_return - return encode(e, val.value) + return encode(e, val.value) } _decode_simple :: proc(r: io.Reader) -> (v: Simple, err: io.Error) { @@ -738,16 +739,16 @@ _encode_nil :: proc(w: io.Writer) -> io.Error { // Streaming encode_stream_begin :: proc(w: io.Writer, major: Major) -> (err: io.Error) { - assert(major >= Major(.Bytes) && major <= Major(.Map), "illegal stream type") + assert(major >= Major(.Bytes) && major <= Major(.Map), "illegal stream type") - header := (u8(major) << 5) | u8(Add.Length_Unknown) - _, err = io.write_full(w, {header}) + header := (u8(major) << 5) | u8(Add.Length_Unknown) + _, err = io.write_full(w, {header}) return } encode_stream_end :: proc(w: io.Writer) -> io.Error { - header := (u8(Major.Other) << 5) | u8(Add.Break) - _, err := io.write_full(w, {header}) + header := (u8(Major.Other) << 5) | u8(Add.Break) + _, err := io.write_full(w, {header}) return err } @@ -756,8 +757,8 @@ encode_stream_text :: _encode_text encode_stream_array_item :: encode encode_stream_map_entry :: proc(e: Encoder, key: Value, val: Value) -> Encode_Error { - encode(e, key) or_return - return encode(e, val) + encode(e, key) or_return + return encode(e, val) } // For `Bytes` and `Text` strings: Decodes the number of items the header says follows. @@ -883,4 +884,4 @@ _encode_deterministic_f64 :: proc(w: io.Writer, v: f64) -> io.Error { } return _encode_f64_exact(w, v) -} +} \ No newline at end of file diff --git a/core/encoding/cbor/doc.odin b/core/encoding/cbor/doc.odin index 937b1b61b..b3fa36130 100644 --- a/core/encoding/cbor/doc.odin +++ b/core/encoding/cbor/doc.odin @@ -77,8 +77,11 @@ You can look at the default tags provided for pointers on how these implementati Example: package main + import "base:intrinsics" + import "core:encoding/cbor" import "core:fmt" + import "core:reflect" import "core:time" Possibilities :: union { @@ -93,9 +96,32 @@ Example: ignore_this: ^Data `cbor:"-"`, // Ignored by implementation. renamed: f32 `cbor:"renamed :)"`, // Renamed when encoded. my_union: Possibilities, // Union support. + + my_raw: [8]u32 `cbor_tag:"raw"`, // Custom tag that just writes the value as bytes. } main :: proc() { + // Example custom tag implementation that instead of breaking down all parts, + // just writes the value as a big byte blob. This is an advanced feature but very powerful. + RAW_TAG_NR :: 200 + cbor.tag_register_number({ + marshal = proc(_: ^cbor.Tag_Implementation, e: cbor.Encoder, v: any) -> cbor.Marshal_Error { + cbor._encode_u8(e.writer, RAW_TAG_NR, .Tag) or_return + return cbor.err_conv(cbor._encode_bytes(e, reflect.as_bytes(v))) + }, + unmarshal = proc(_: ^cbor.Tag_Implementation, d: cbor.Decoder, _: cbor.Tag_Number, v: any) -> (cbor.Unmarshal_Error) { + hdr := cbor._decode_header(d.reader) or_return + maj, add := cbor._header_split(hdr) + if maj != .Bytes { + return .Bad_Tag_Value + } + + bytes := cbor.err_conv(cbor._decode_bytes(d, add, maj)) or_return + intrinsics.mem_copy_non_overlapping(v.data, raw_data(bytes), len(bytes)) + return nil + }, + }, RAW_TAG_NR, "raw") + now := time.Time{_nsec = 1701117968 * 1e9} data := Data{ @@ -105,21 +131,22 @@ Example: ignore_this = &Data{}, renamed = 123123.125, my_union = 3, + my_raw = {1=1, 2=2, 3=3}, } - + // Marshal the struct into binary CBOR. binary, err := cbor.marshal(data, cbor.ENCODE_FULLY_DETERMINISTIC) - assert(err == nil) + fmt.assertf(err == nil, "marshal error: %v", err) defer delete(binary) - + // Decode the binary data into a `cbor.Value`. decoded, derr := cbor.decode(string(binary)) - assert(derr == nil) + fmt.assertf(derr == nil, "decode error: %v", derr) defer cbor.destroy(decoded) // Turn the CBOR into a human readable representation defined as the diagnostic format in [[RFC 8949 Section 8;https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation]]. diagnosis, eerr := cbor.to_diagnostic_format(decoded) - assert(eerr == nil) + fmt.assertf(eerr == nil, "to diagnostic error: %v", eerr) defer delete(diagnosis) fmt.println(diagnosis) @@ -127,6 +154,7 @@ Example: Output: { + "my_raw": 200(h'00001000200030000000000000000000'), "my_union": 1010([ "int", 3 diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index 37c9dd180..6657807f5 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -45,8 +45,8 @@ marshal :: marshal_into // Marshals the given value into a CBOR byte stream (allocated using the given allocator). // See docs on the `marshal_into` proc group for more info. -marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (bytes: []byte, err: Marshal_Error) { - b, alloc_err := strings.builder_make(allocator) +marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (bytes: []byte, err: Marshal_Error) { + b, alloc_err := strings.builder_make(allocator, loc=loc) // The builder as a stream also returns .EOF if it ran out of memory so this is consistent. if alloc_err != nil { return nil, .EOF @@ -85,7 +85,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { if .Self_Described_CBOR in e.flags { err_conv(_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag)) or_return - e.flags &~= { .Self_Described_CBOR } + e.flags -= { .Self_Described_CBOR } } if v == nil { @@ -97,11 +97,14 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { return impl->marshal(e, v) } - ti := runtime.type_info_base(type_info_of(v.id)) - a := any{v.data, ti.id} + ti := runtime.type_info_core(type_info_of(v.id)) + return _marshal_into_encoder(e, v, ti) +} +_marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (err: Marshal_Error) { + a := any{v.data, ti.id} #partial switch info in ti.variant { - case runtime.Type_Info_Named: + case runtime.Type_Info_Named, runtime.Type_Info_Enum, runtime.Type_Info_Bit_Field: unreachable() case runtime.Type_Info_Pointer: @@ -223,18 +226,38 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { } err_conv(_encode_u64(e, u64(info.count), .Array)) or_return + + if impl, ok := _tag_implementations_type[info.elem.id]; ok { + for i in 0..marshal(e, any{rawptr(data), info.elem.id}) or_return + } + return + } + + elem_ti := runtime.type_info_core(type_info_of(info.elem.id)) for i in 0..marshal(e, any{rawptr(data), info.elem.id}) or_return + } + return + } + + elem_ti := runtime.type_info_core(type_info_of(info.elem.id)) for i in 0.. (err: Marshal_Error) { array := (^mem.Raw_Dynamic_Array)(v.data) err_conv(_encode_u64(e, u64(array.len), .Array)) or_return + + if impl, ok := _tag_implementations_type[info.elem.id]; ok { + for i in 0..marshal(e, any{rawptr(data), info.elem.id}) or_return + } + return + } + + elem_ti := runtime.type_info_core(type_info_of(info.elem.id)) for i in 0.. (err: Marshal_Error) { array := (^mem.Raw_Slice)(v.data) err_conv(_encode_u64(e, u64(array.len), .Array)) or_return + + if impl, ok := _tag_implementations_type[info.elem.id]; ok { + for i in 0..marshal(e, any{rawptr(data), info.elem.id}) or_return + } + return + } + + elem_ti := runtime.type_info_core(type_info_of(info.elem.id)) for i in 0.. (err: Marshal_Error) { builder := strings.builder_from_slice(res[:]) e.writer = strings.to_stream(&builder) - assert(_encode_u64(e, u64(len(str)), .Text) == nil) + err := _encode_u64(e, u64(len(str)), .Text) + assert(err == nil) res[9] = u8(len(builder.buf)) assert(res[9] < 10) return @@ -463,7 +507,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { } n: u64; { - for _, i in info.names { + for _, i in info.names[:info.field_count] { if field_name(info, i) != "-" { n += 1 } @@ -479,7 +523,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { entries := make([dynamic]Name, 0, n, e.temp_allocator) or_return defer delete(entries) - for _, i in info.names { + for _, i in info.names[:info.field_count] { fname := field_name(info, i) if fname == "-" { continue @@ -497,7 +541,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { marshal_entry(e, info, v, entry.name, entry.field) or_return } } else { - for _, i in info.names { + for _, i in info.names[:info.field_count] { fname := field_name(info, i) if fname == "-" { continue @@ -542,9 +586,6 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { return marshal_into(e, any{v.data, vti.id}) - case runtime.Type_Info_Enum: - return marshal_into(e, any{v.data, info.base.id}) - case runtime.Type_Info_Bit_Set: // Store bit_set as big endian just like the protocol. do_byte_swap := !reflect.bit_set_is_big_endian(v) diff --git a/core/encoding/cbor/tags.odin b/core/encoding/cbor/tags.odin index 3dc79a5dd..17420af46 100644 --- a/core/encoding/cbor/tags.odin +++ b/core/encoding/cbor/tags.odin @@ -95,7 +95,6 @@ tag_register_number :: proc(impl: Tag_Implementation, nr: Tag_Number, id: string } // Controls initialization of default tag implementations. -// JS and WASI default to a panic allocator so we don't want to do it on those. INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, !ODIN_DEFAULT_TO_PANIC_ALLOCATOR && !ODIN_DEFAULT_TO_NIL_ALLOCATOR) @(private, init, disabled=!INITIALIZE_DEFAULT_TAGS) diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index a1524d9f4..c54660839 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -31,8 +31,8 @@ unmarshal :: proc { unmarshal_from_string, } -unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) { - err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator) +unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { + err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator, loc) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } @@ -40,21 +40,21 @@ unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, } // Unmarshals from a string, see docs on the proc group `Unmarshal` for more info. -unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) { +unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { sr: strings.Reader r := strings.to_reader(&sr, s) - err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator) + err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator, loc) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } return } -unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) { +unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { d := d - err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator) + err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator, loc) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } @@ -62,7 +62,7 @@ unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.alloca } -_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator) -> Unmarshal_Error { +_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> Unmarshal_Error { context.allocator = allocator context.temp_allocator = temp_allocator v := v @@ -78,10 +78,10 @@ _unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocat } data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id} - return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return)) + return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return), allocator, temp_allocator, loc) } -_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Error) { +_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { v := v ti := reflect.type_info_base(type_info_of(v.id)) r := d.reader @@ -96,7 +96,8 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err ti = reflect.type_info_base(variant) if !reflect.is_pointer_internally(variant) { tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id} - assert(_assign_int(tag, 1)) + assigned := _assign_int(tag, 1) + assert(assigned) } } } @@ -104,7 +105,7 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err // Allow generic unmarshal by doing it into a `Value`. switch &dst in v { case Value: - dst = err_conv(_decode_from_decoder(d, hdr)) or_return + dst = err_conv(_decode_from_decoder(d, hdr, allocator, loc)) or_return return } @@ -273,13 +274,13 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err // NOTE: Because this is a special type and not to be treated as a general integer, // We only put the value of it in fields that are explicitly of type `Simple`. - switch &dst in v { - case Simple: - dst = decoded - return - case: - return _unsupported(v, hdr, add) - } + switch &dst in v { + case Simple: + dst = decoded + return + case: + return _unsupported(v, hdr, add) + } case .Tag: switch &dst in v { @@ -308,7 +309,7 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err if impl, ok := _tag_implementations_nr[nr]; ok { return impl->unmarshal(d, nr, v) } else if nr == TAG_OBJECT_TYPE { - return _unmarshal_union(d, v, ti, hdr) + return _unmarshal_union(d, v, ti, hdr, loc=loc) } else { // Discard the tag info and unmarshal as its value. return _unmarshal_value(d, v, _decode_header(r) or_return) @@ -316,19 +317,19 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err return _unsupported(v, hdr, add) - case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add) - case .Text: return _unmarshal_string(d, v, ti, hdr, add) - case .Array: return _unmarshal_array(d, v, ti, hdr, add) - case .Map: return _unmarshal_map(d, v, ti, hdr, add) + case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add, allocator=allocator, loc=loc) + case .Text: return _unmarshal_string(d, v, ti, hdr, add, allocator=allocator, loc=loc) + case .Array: return _unmarshal_array(d, v, ti, hdr, add, allocator=allocator, loc=loc) + case .Map: return _unmarshal_map(d, v, ti, hdr, add, allocator=allocator, loc=loc) case: return .Bad_Major } } -_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { +_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) { #partial switch t in ti.variant { case reflect.Type_Info_String: - bytes := err_conv(_decode_bytes(d, add)) or_return + bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return if t.is_cstring { raw := (^cstring)(v.data) @@ -347,7 +348,7 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if elem_base.id != byte { return _unsupported(v, hdr) } - bytes := err_conv(_decode_bytes(d, add)) or_return + bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return raw := (^mem.Raw_Slice)(v.data) raw^ = transmute(mem.Raw_Slice)bytes return @@ -357,12 +358,12 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if elem_base.id != byte { return _unsupported(v, hdr) } - bytes := err_conv(_decode_bytes(d, add)) or_return + bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return raw := (^mem.Raw_Dynamic_Array)(v.data) raw.data = raw_data(bytes) raw.len = len(bytes) raw.cap = len(bytes) - raw.allocator = context.allocator + raw.allocator = allocator return case reflect.Type_Info_Array: @@ -385,10 +386,10 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return _unsupported(v, hdr) } -_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { +_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { #partial switch t in ti.variant { case reflect.Type_Info_String: - text := err_conv(_decode_text(d, add)) or_return + text := err_conv(_decode_text(d, add, allocator, loc)) or_return if t.is_cstring { raw := (^cstring)(v.data) @@ -403,8 +404,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade // Enum by its variant name. case reflect.Type_Info_Enum: - text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return - defer delete(text, context.temp_allocator) + text := err_conv(_decode_text(d, add, allocator=temp_allocator, loc=loc)) or_return + defer delete(text, temp_allocator, loc) for name, i in t.names { if name == text { @@ -414,8 +415,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade } case reflect.Type_Info_Rune: - text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return - defer delete(text, context.temp_allocator) + text := err_conv(_decode_text(d, add, allocator=temp_allocator, loc=loc)) or_return + defer delete(text, temp_allocator, loc) r := (^rune)(v.data) dr, n := utf8.decode_rune(text) @@ -430,13 +431,15 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade return _unsupported(v, hdr) } -_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { +_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) { assign_array :: proc( d: Decoder, da: ^mem.Raw_Dynamic_Array, elemt: ^reflect.Type_Info, length: int, growable := true, + allocator := context.allocator, + loc := #caller_location, ) -> (out_of_space: bool, err: Unmarshal_Error) { for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 { elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size)) @@ -450,13 +453,13 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if !growable { return true, .Out_Of_Memory } cap := 2 * da.cap - ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap) + ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap, loc) // NOTE: Might be lying here, but it is at least an allocator error. if !ok { return false, .Out_Of_Memory } } - err = _unmarshal_value(d, elem, hdr) + err = _unmarshal_value(d, elem, hdr, allocator=allocator, loc=loc) if length == -1 && err == .Break { break } if err != nil { return } @@ -469,10 +472,10 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header // Allow generically storing the values array. switch &dst in v { case ^Array: - dst = err_conv(_decode_array_ptr(d, add)) or_return + dst = err_conv(_decode_array_ptr(d, add, allocator=allocator, loc=loc)) or_return return case Array: - dst = err_conv(_decode_array(d, add)) or_return + dst = err_conv(_decode_array(d, add, allocator=allocator, loc=loc)) or_return return } @@ -480,8 +483,8 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header case reflect.Type_Info_Slice: length, scap := err_conv(_decode_len_container(d, add)) or_return - data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return - defer if err != nil { mem.free_bytes(data) } + data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return + defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) } da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator } @@ -489,7 +492,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if .Shrink_Excess in d.flags { // Ignoring an error here, but this is not critical to succeed. - _ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len) + _ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len, loc=loc) } raw := (^mem.Raw_Slice)(v.data) @@ -500,8 +503,8 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header case reflect.Type_Info_Dynamic_Array: length, scap := err_conv(_decode_len_container(d, add)) or_return - data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return - defer if err != nil { mem.free_bytes(data) } + data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, loc=loc) or_return + defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) } raw := (^mem.Raw_Dynamic_Array)(v.data) raw.data = raw_data(data) @@ -513,47 +516,41 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if .Shrink_Excess in d.flags { // Ignoring an error here, but this is not critical to succeed. - _ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len) + _ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len, loc=loc) } return case reflect.Type_Info_Array: - _, scap := err_conv(_decode_len_container(d, add)) or_return - length := min(scap, t.count) - + length, _ := err_conv(_decode_len_container(d, add)) or_return if length > t.count { return _unsupported(v, hdr) } - da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator } + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator } out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return if out_of_space { return _unsupported(v, hdr) } return case reflect.Type_Info_Enumerated_Array: - _, scap := err_conv(_decode_len_container(d, add)) or_return - length := min(scap, t.count) - + length, _ := err_conv(_decode_len_container(d, add)) or_return if length > t.count { return _unsupported(v, hdr) } - da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator } + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator } out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return if out_of_space { return _unsupported(v, hdr) } return case reflect.Type_Info_Complex: - _, scap := err_conv(_decode_len_container(d, add)) or_return - length := min(scap, 2) - + length, _ := err_conv(_decode_len_container(d, add)) or_return if length > 2 { return _unsupported(v, hdr) } - da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, context.allocator } + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, allocator } info: ^runtime.Type_Info switch ti.id { @@ -568,14 +565,12 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return case reflect.Type_Info_Quaternion: - _, scap := err_conv(_decode_len_container(d, add)) or_return - length := min(scap, 4) - + length, _ := err_conv(_decode_len_container(d, add)) or_return if length > 4 { return _unsupported(v, hdr) } - da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, context.allocator } + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, allocator } info: ^runtime.Type_Info switch ti.id { @@ -593,17 +588,17 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header } } -_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { +_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) { r := d.reader - decode_key :: proc(d: Decoder, v: any, allocator := context.allocator) -> (k: string, err: Unmarshal_Error) { + decode_key :: proc(d: Decoder, v: any, allocator := context.allocator, loc := #caller_location) -> (k: string, err: Unmarshal_Error) { entry_hdr := _decode_header(d.reader) or_return entry_maj, entry_add := _header_split(entry_hdr) #partial switch entry_maj { case .Text: - k = err_conv(_decode_text(d, entry_add, allocator)) or_return + k = err_conv(_decode_text(d, entry_add, allocator=allocator, loc=loc)) or_return return case .Bytes: - bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator)) or_return + bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator, loc=loc)) or_return k = string(bytes) return case: @@ -615,23 +610,23 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, // Allow generically storing the map array. switch &dst in v { case ^Map: - dst = err_conv(_decode_map_ptr(d, add)) or_return + dst = err_conv(_decode_map_ptr(d, add, allocator=allocator, loc=loc)) or_return return case Map: - dst = err_conv(_decode_map(d, add)) or_return + dst = err_conv(_decode_map(d, add, allocator=allocator, loc=loc)) or_return return } #partial switch t in ti.variant { case reflect.Type_Info_Struct: - if t.is_raw_union { + if .raw_union in t.flags { return _unsupported(v, hdr) } length, _ := err_conv(_decode_len_container(d, add)) or_return unknown := length == -1 fields := reflect.struct_fields_zipped(ti.id) - + for idx := 0; idx < len(fields) && (unknown || idx < length); idx += 1 { // Decode key, keys can only be strings. key: string @@ -644,7 +639,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, key = keyv } defer delete(key, context.temp_allocator) - + // Find matching field. use_field_idx := -1 { @@ -754,7 +749,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, // Unmarshal into a union, based on the `TAG_OBJECT_TYPE` tag of the spec, it denotes a tag which // contains an array of exactly two elements, the first is a textual representation of the following // CBOR value's type. -_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header) -> (err: Unmarshal_Error) { +_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, loc := #caller_location) -> (err: Unmarshal_Error) { r := d.reader #partial switch t in ti.variant { case reflect.Type_Info_Union: @@ -792,7 +787,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header case reflect.Type_Info_Named: if vti.name == target_name { reflect.set_union_variant_raw_tag(v, tag) - return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return) + return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return, loc=loc) } case: @@ -804,7 +799,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if variant_name == target_name { reflect.set_union_variant_raw_tag(v, tag) - return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return) + return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return, loc=loc) } } } diff --git a/core/encoding/csv/example.odin b/core/encoding/csv/example.odin new file mode 100644 index 000000000..24722589d --- /dev/null +++ b/core/encoding/csv/example.odin @@ -0,0 +1,88 @@ +//+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, errno := os.open(filename) + if errno != os.ERROR_NONE { + fmt.printfln("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 f8c72c423..ebc7b39a0 100644 --- a/core/encoding/csv/reader.odin +++ b/core/encoding/csv/reader.odin @@ -57,6 +57,9 @@ Reader :: struct { field_indices: [dynamic]int, last_record: [dynamic]string, sr: strings.Reader, // used by reader_init_with_string + + // Set and used by the iterator. Query using `iterator_last_error` + last_iterator_error: Error, } @@ -121,6 +124,27 @@ reader_destroy :: proc(r: ^Reader) { bufio.reader_destroy(&r.r) } +/* + Returns a record at a time. + + for record, row_idx in csv.iterator_next(&r) { ... } + + TIP: If you process the results within the loop and don't need to own the results, + you can set the Reader's `reuse_record` and `reuse_record_reuse_record_buffer` to true; + you won't need to delete the record or its fields. +*/ +iterator_next :: proc(r: ^Reader) -> (record: []string, idx: int, err: Error, more: bool) { + record, r.last_iterator_error = read(r) + return record, r.line_count - 1, r.last_iterator_error, r.last_iterator_error == nil +} + +// Get last CSV parse error if we ignored it in the iterator loop +// +// for record, row_idx in csv.iterator_next(&r) { ... } +iterator_last_error :: proc(r: Reader) -> (err: Error) { + return r.last_iterator_error +} + // read reads a single record (a slice of fields) from r // // All \r\n sequences are normalized to \n, including multi-line field @@ -147,7 +171,7 @@ is_io_error :: proc(err: Error, io_err: io.Error) -> bool { // read_all reads all the remaining records from r. // Each record is a slice of fields. -// read_all is defined to read until an EOF, and does not treat, and does not treat EOF as an error +// read_all is defined to read until an EOF, and does not treat EOF as an error @(require_results) read_all :: proc(r: ^Reader, allocator := context.allocator) -> ([][]string, Error) { context.allocator = allocator @@ -460,5 +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/entity/entity.odin b/core/encoding/entity/entity.odin index cee6230ef..f5208ad6f 100644 --- a/core/encoding/entity/entity.odin +++ b/core/encoding/entity/entity.odin @@ -56,38 +56,27 @@ CDATA_END :: "]]>" COMMENT_START :: "" -/* - Default: CDATA and comments are passed through unchanged. -*/ +// Default: CDATA and comments are passed through unchanged. XML_Decode_Option :: enum u8 { - /* - Do not decode & entities. It decodes by default. - If given, overrides `Decode_CDATA`. - */ + // Do not decode & entities. It decodes by default. If given, overrides `Decode_CDATA`. No_Entity_Decode, - /* - CDATA is unboxed. - */ + // CDATA is unboxed. Unbox_CDATA, - /* - Unboxed CDATA is decoded as well. - Ignored if `.Unbox_CDATA` is not given. - */ + // Unboxed CDATA is decoded as well. Ignored if `.Unbox_CDATA` is not given. Decode_CDATA, - /* - Comments are stripped. - */ + // Comments are stripped. Comment_Strip, + + // Normalize whitespace + Normalize_Whitespace, } XML_Decode_Options :: bit_set[XML_Decode_Option; u8] -/* - Decode a string that may include SGML/XML/HTML entities. - The caller has to free the result. -*/ +// Decode a string that may include SGML/XML/HTML entities. +// The caller has to free the result. decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := context.allocator) -> (decoded: string, err: Error) { context.allocator = allocator @@ -100,14 +89,14 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := t := Tokenizer{src=input} in_data := false + prev: rune = ' ' + loop: for { advance(&t) or_return if t.r < 0 { break loop } - /* - Below here we're never inside a CDATA tag. - At most we'll see the start of one, but that doesn't affect the logic. - */ + // Below here we're never inside a CDATA tag. At most we'll see the start of one, + // but that doesn't affect the logic. switch t.r { case '<': /* @@ -126,9 +115,7 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := in_data = _handle_xml_special(&t, &builder, options) or_return case ']': - /* - If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag. - */ + // If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag. if in_data { if t.read_offset + len(CDATA_END) < len(t.src) { if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END { @@ -143,22 +130,16 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := case: if in_data && .Decode_CDATA not_in options { - /* - Unboxed, but undecoded. - */ + // Unboxed, but undecoded. write_rune(&builder, t.r) continue } if t.r == '&' { if entity, entity_err := _extract_xml_entity(&t); entity_err != .None { - /* - We read to the end of the string without closing the entity. - Pass through as-is. - */ + // We read to the end of the string without closing the entity. Pass through as-is. write_string(&builder, entity) } else { - if .No_Entity_Decode not_in options { if decoded, ok := xml_decode_entity(entity); ok { write_rune(&builder, decoded) @@ -166,19 +147,41 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := } } - /* - Literal passthrough because the decode failed or we want entities not decoded. - */ + // Literal passthrough because the decode failed or we want entities not decoded. write_string(&builder, "&") write_string(&builder, entity) write_string(&builder, ";") } } else { - write_rune(&builder, t.r) + // Handle AV Normalization: https://www.w3.org/TR/2006/REC-xml11-20060816/#AVNormalize + if .Normalize_Whitespace in options { + switch t.r { + case ' ', '\r', '\n', '\t': + if prev != ' ' { + write_rune(&builder, ' ') + prev = ' ' + } + case: + write_rune(&builder, t.r) + prev = t.r + } + } else { + // https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-line-ends + switch t.r { + case '\n', 0x85, 0x2028: + write_rune(&builder, '\n') + case '\r': // Do nothing until next character + case: + if prev == '\r' { // Turn a single carriage return into a \n + write_rune(&builder, '\n') + } + write_rune(&builder, t.r) + } + prev = t.r + } } } } - return strings.clone(strings.to_string(builder), allocator), err } @@ -253,24 +256,18 @@ xml_decode_entity :: proc(entity: string) -> (decoded: rune, ok: bool) { return rune(val), true case: - /* - Named entity. - */ + // Named entity. return named_xml_entity_to_rune(entity) } } -/* - Private XML helper to extract `&;` entity. -*/ +// Private XML helper to extract `&;` entity. @(private="file") _extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) { assert(t != nil && t.r == '&') - /* - All of these would be in the ASCII range. - Even if one is not, it doesn't matter. All characters we need to compare to extract are. - */ + // All of these would be in the ASCII range. + // Even if one is not, it doesn't matter. All characters we need to compare to extract are. length := len(t.src) found := false @@ -292,9 +289,7 @@ _extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) { return string(t.src[t.offset : t.read_offset]), .Invalid_Entity_Encoding } -/* - Private XML helper for CDATA and comments. -*/ +// Private XML helper for CDATA and comments. @(private="file") _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: XML_Decode_Options) -> (in_data: bool, err: Error) { assert(t != nil && t.r == '<') @@ -304,20 +299,14 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X t.read_offset += len(CDATA_START) - 1 if .Unbox_CDATA in options && .Decode_CDATA in options { - /* - We're unboxing _and_ decoding CDATA - */ + // We're unboxing _and_ decoding CDATA return true, .None } - /* - CDATA is passed through. - */ + // CDATA is passed through. offset := t.offset - /* - Scan until end of CDATA. - */ + // Scan until end of CDATA. for { advance(t) or_return if t.r < 0 { return true, .CDATA_Not_Terminated } @@ -341,14 +330,10 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X } else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START { t.read_offset += len(COMMENT_START) - /* - Comment is passed through by default. - */ + // Comment is passed through by default. offset := t.offset - /* - Scan until end of Comment. - */ + // Scan until end of Comment. for { advance(t) or_return if t.r < 0 { return true, .Comment_Not_Terminated } diff --git a/core/encoding/entity/generated.odin b/core/encoding/entity/generated.odin index d2acde20d..0c4742149 100644 --- a/core/encoding/entity/generated.odin +++ b/core/encoding/entity/generated.odin @@ -61,5026 +61,5026 @@ named_xml_entity_to_rune :: proc(name: string) -> (decoded: rune, ok: bool) { case 'A': switch name { - case "AElig": // LATIN CAPITAL LETTER AE - return rune(0xc6), true - case "AMP": // AMPERSAND - return rune(0x26), true - case "Aacgr": // GREEK CAPITAL LETTER ALPHA WITH TONOS - return rune(0x0386), true - case "Aacute": // LATIN CAPITAL LETTER A WITH ACUTE - return rune(0xc1), true - case "Abreve": // LATIN CAPITAL LETTER A WITH BREVE - return rune(0x0102), true - case "Acirc": // LATIN CAPITAL LETTER A WITH CIRCUMFLEX - return rune(0xc2), true - case "Acy": // CYRILLIC CAPITAL LETTER A - return rune(0x0410), true - case "Afr": // MATHEMATICAL FRAKTUR CAPITAL A - return rune(0x01d504), true - case "Agr": // GREEK CAPITAL LETTER ALPHA - return rune(0x0391), true - case "Agrave": // LATIN CAPITAL LETTER A WITH GRAVE - return rune(0xc0), true - case "Alpha": // GREEK CAPITAL LETTER ALPHA - return rune(0x0391), true - case "Amacr": // LATIN CAPITAL LETTER A WITH MACRON - return rune(0x0100), true - case "And": // DOUBLE LOGICAL AND - return rune(0x2a53), true - case "Aogon": // LATIN CAPITAL LETTER A WITH OGONEK - return rune(0x0104), true - case "Aopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL A - return rune(0x01d538), true - case "ApplyFunction": // FUNCTION APPLICATION - return rune(0x2061), true - case "Aring": // LATIN CAPITAL LETTER A WITH RING ABOVE - return rune(0xc5), true - case "Ascr": // MATHEMATICAL SCRIPT CAPITAL A - return rune(0x01d49c), true - case "Assign": // COLON EQUALS - return rune(0x2254), true - case "Ast": // TWO ASTERISKS ALIGNED VERTICALLY - return rune(0x2051), true - case "Atilde": // LATIN CAPITAL LETTER A WITH TILDE - return rune(0xc3), true - case "Auml": // LATIN CAPITAL LETTER A WITH DIAERESIS - return rune(0xc4), true + case "AElig": // LATIN CAPITAL LETTER AE + return rune(0xc6), true + case "AMP": // AMPERSAND + return rune(0x26), true + case "Aacgr": // GREEK CAPITAL LETTER ALPHA WITH TONOS + return rune(0x0386), true + case "Aacute": // LATIN CAPITAL LETTER A WITH ACUTE + return rune(0xc1), true + case "Abreve": // LATIN CAPITAL LETTER A WITH BREVE + return rune(0x0102), true + case "Acirc": // LATIN CAPITAL LETTER A WITH CIRCUMFLEX + return rune(0xc2), true + case "Acy": // CYRILLIC CAPITAL LETTER A + return rune(0x0410), true + case "Afr": // MATHEMATICAL FRAKTUR CAPITAL A + return rune(0x01d504), true + case "Agr": // GREEK CAPITAL LETTER ALPHA + return rune(0x0391), true + case "Agrave": // LATIN CAPITAL LETTER A WITH GRAVE + return rune(0xc0), true + case "Alpha": // GREEK CAPITAL LETTER ALPHA + return rune(0x0391), true + case "Amacr": // LATIN CAPITAL LETTER A WITH MACRON + return rune(0x0100), true + case "And": // DOUBLE LOGICAL AND + return rune(0x2a53), true + case "Aogon": // LATIN CAPITAL LETTER A WITH OGONEK + return rune(0x0104), true + case "Aopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL A + return rune(0x01d538), true + case "ApplyFunction": // FUNCTION APPLICATION + return rune(0x2061), true + case "Aring": // LATIN CAPITAL LETTER A WITH RING ABOVE + return rune(0xc5), true + case "Ascr": // MATHEMATICAL SCRIPT CAPITAL A + return rune(0x01d49c), true + case "Assign": // COLON EQUALS + return rune(0x2254), true + case "Ast": // TWO ASTERISKS ALIGNED VERTICALLY + return rune(0x2051), true + case "Atilde": // LATIN CAPITAL LETTER A WITH TILDE + return rune(0xc3), true + case "Auml": // LATIN CAPITAL LETTER A WITH DIAERESIS + return rune(0xc4), true } case 'B': switch name { - case "Backslash": // SET MINUS - return rune(0x2216), true - case "Barint": // INTEGRAL WITH DOUBLE STROKE - return rune(0x2a0e), true - case "Barv": // SHORT DOWN TACK WITH OVERBAR - return rune(0x2ae7), true - case "Barwed": // PERSPECTIVE - return rune(0x2306), true - case "Barwedl": // LOGICAL AND WITH DOUBLE OVERBAR - return rune(0x2a5e), true - case "Bcy": // CYRILLIC CAPITAL LETTER BE - return rune(0x0411), true - case "Because": // BECAUSE - return rune(0x2235), true - case "Bernoullis": // SCRIPT CAPITAL B - return rune(0x212c), true - case "Beta": // GREEK CAPITAL LETTER BETA - return rune(0x0392), true - case "Bfr": // MATHEMATICAL FRAKTUR CAPITAL B - return rune(0x01d505), true - case "Bgr": // GREEK CAPITAL LETTER BETA - return rune(0x0392), true - case "Bopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL B - return rune(0x01d539), true - case "Breve": // BREVE - return rune(0x02d8), true - case "Bscr": // SCRIPT CAPITAL B - return rune(0x212c), true - case "Bumpeq": // GEOMETRICALLY EQUIVALENT TO - return rune(0x224e), true - case "Bvert": // BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL - return rune(0x2506), true + case "Backslash": // SET MINUS + return rune(0x2216), true + case "Barint": // INTEGRAL WITH DOUBLE STROKE + return rune(0x2a0e), true + case "Barv": // SHORT DOWN TACK WITH OVERBAR + return rune(0x2ae7), true + case "Barwed": // PERSPECTIVE + return rune(0x2306), true + case "Barwedl": // LOGICAL AND WITH DOUBLE OVERBAR + return rune(0x2a5e), true + case "Bcy": // CYRILLIC CAPITAL LETTER BE + return rune(0x0411), true + case "Because": // BECAUSE + return rune(0x2235), true + case "Bernoullis": // SCRIPT CAPITAL B + return rune(0x212c), true + case "Beta": // GREEK CAPITAL LETTER BETA + return rune(0x0392), true + case "Bfr": // MATHEMATICAL FRAKTUR CAPITAL B + return rune(0x01d505), true + case "Bgr": // GREEK CAPITAL LETTER BETA + return rune(0x0392), true + case "Bopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL B + return rune(0x01d539), true + case "Breve": // BREVE + return rune(0x02d8), true + case "Bscr": // SCRIPT CAPITAL B + return rune(0x212c), true + case "Bumpeq": // GEOMETRICALLY EQUIVALENT TO + return rune(0x224e), true + case "Bvert": // BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL + return rune(0x2506), true } case 'C': switch name { - case "CHcy": // CYRILLIC CAPITAL LETTER CHE - return rune(0x0427), true - case "COPY": // COPYRIGHT SIGN - return rune(0xa9), true - case "Cacute": // LATIN CAPITAL LETTER C WITH ACUTE - return rune(0x0106), true - case "Cap": // DOUBLE INTERSECTION - return rune(0x22d2), true - case "CapitalDifferentialD": // DOUBLE-STRUCK ITALIC CAPITAL D - return rune(0x2145), true - case "Cayleys": // BLACK-LETTER CAPITAL C - return rune(0x212d), true - case "Ccaron": // LATIN CAPITAL LETTER C WITH CARON - return rune(0x010c), true - case "Ccedil": // LATIN CAPITAL LETTER C WITH CEDILLA - return rune(0xc7), true - case "Ccirc": // LATIN CAPITAL LETTER C WITH CIRCUMFLEX - return rune(0x0108), true - case "Cconint": // VOLUME INTEGRAL - return rune(0x2230), true - case "Cdot": // LATIN CAPITAL LETTER C WITH DOT ABOVE - return rune(0x010a), true - case "Cedilla": // CEDILLA - return rune(0xb8), true - case "CenterDot": // MIDDLE DOT - return rune(0xb7), true - case "Cfr": // BLACK-LETTER CAPITAL C - return rune(0x212d), true - case "Chi": // GREEK CAPITAL LETTER CHI - return rune(0x03a7), true - case "CircleDot": // CIRCLED DOT OPERATOR - return rune(0x2299), true - case "CircleMinus": // CIRCLED MINUS - return rune(0x2296), true - case "CirclePlus": // CIRCLED PLUS - return rune(0x2295), true - case "CircleTimes": // CIRCLED TIMES - return rune(0x2297), true - case "ClockwiseContourIntegral": // CLOCKWISE CONTOUR INTEGRAL - return rune(0x2232), true - case "CloseCurlyDoubleQuote": // RIGHT DOUBLE QUOTATION MARK - return rune(0x201d), true - case "CloseCurlyQuote": // RIGHT SINGLE QUOTATION MARK - return rune(0x2019), true - case "Colon": // PROPORTION - return rune(0x2237), true - case "Colone": // DOUBLE COLON EQUAL - return rune(0x2a74), true - case "Congruent": // IDENTICAL TO - return rune(0x2261), true - case "Conint": // SURFACE INTEGRAL - return rune(0x222f), true - case "ContourIntegral": // CONTOUR INTEGRAL - return rune(0x222e), true - case "Copf": // DOUBLE-STRUCK CAPITAL C - return rune(0x2102), true - case "Coproduct": // N-ARY COPRODUCT - return rune(0x2210), true - case "CounterClockwiseContourIntegral": // ANTICLOCKWISE CONTOUR INTEGRAL - return rune(0x2233), true - case "Cross": // VECTOR OR CROSS PRODUCT - return rune(0x2a2f), true - case "Cscr": // MATHEMATICAL SCRIPT CAPITAL C - return rune(0x01d49e), true - case "Cup": // DOUBLE UNION - return rune(0x22d3), true - case "CupCap": // EQUIVALENT TO - return rune(0x224d), true + case "CHcy": // CYRILLIC CAPITAL LETTER CHE + return rune(0x0427), true + case "COPY": // COPYRIGHT SIGN + return rune(0xa9), true + case "Cacute": // LATIN CAPITAL LETTER C WITH ACUTE + return rune(0x0106), true + case "Cap": // DOUBLE INTERSECTION + return rune(0x22d2), true + case "CapitalDifferentialD": // DOUBLE-STRUCK ITALIC CAPITAL D + return rune(0x2145), true + case "Cayleys": // BLACK-LETTER CAPITAL C + return rune(0x212d), true + case "Ccaron": // LATIN CAPITAL LETTER C WITH CARON + return rune(0x010c), true + case "Ccedil": // LATIN CAPITAL LETTER C WITH CEDILLA + return rune(0xc7), true + case "Ccirc": // LATIN CAPITAL LETTER C WITH CIRCUMFLEX + return rune(0x0108), true + case "Cconint": // VOLUME INTEGRAL + return rune(0x2230), true + case "Cdot": // LATIN CAPITAL LETTER C WITH DOT ABOVE + return rune(0x010a), true + case "Cedilla": // CEDILLA + return rune(0xb8), true + case "CenterDot": // MIDDLE DOT + return rune(0xb7), true + case "Cfr": // BLACK-LETTER CAPITAL C + return rune(0x212d), true + case "Chi": // GREEK CAPITAL LETTER CHI + return rune(0x03a7), true + case "CircleDot": // CIRCLED DOT OPERATOR + return rune(0x2299), true + case "CircleMinus": // CIRCLED MINUS + return rune(0x2296), true + case "CirclePlus": // CIRCLED PLUS + return rune(0x2295), true + case "CircleTimes": // CIRCLED TIMES + return rune(0x2297), true + case "ClockwiseContourIntegral": // CLOCKWISE CONTOUR INTEGRAL + return rune(0x2232), true + case "CloseCurlyDoubleQuote": // RIGHT DOUBLE QUOTATION MARK + return rune(0x201d), true + case "CloseCurlyQuote": // RIGHT SINGLE QUOTATION MARK + return rune(0x2019), true + case "Colon": // PROPORTION + return rune(0x2237), true + case "Colone": // DOUBLE COLON EQUAL + return rune(0x2a74), true + case "Congruent": // IDENTICAL TO + return rune(0x2261), true + case "Conint": // SURFACE INTEGRAL + return rune(0x222f), true + case "ContourIntegral": // CONTOUR INTEGRAL + return rune(0x222e), true + case "Copf": // DOUBLE-STRUCK CAPITAL C + return rune(0x2102), true + case "Coproduct": // N-ARY COPRODUCT + return rune(0x2210), true + case "CounterClockwiseContourIntegral": // ANTICLOCKWISE CONTOUR INTEGRAL + return rune(0x2233), true + case "Cross": // VECTOR OR CROSS PRODUCT + return rune(0x2a2f), true + case "Cscr": // MATHEMATICAL SCRIPT CAPITAL C + return rune(0x01d49e), true + case "Cup": // DOUBLE UNION + return rune(0x22d3), true + case "CupCap": // EQUIVALENT TO + return rune(0x224d), true } case 'D': switch name { - case "DD": // DOUBLE-STRUCK ITALIC CAPITAL D - return rune(0x2145), true - case "DDotrahd": // RIGHTWARDS ARROW WITH DOTTED STEM - return rune(0x2911), true - case "DJcy": // CYRILLIC CAPITAL LETTER DJE - return rune(0x0402), true - case "DScy": // CYRILLIC CAPITAL LETTER DZE - return rune(0x0405), true - case "DZcy": // CYRILLIC CAPITAL LETTER DZHE - return rune(0x040f), true - case "Dagger": // DOUBLE DAGGER - return rune(0x2021), true - case "Darr": // DOWNWARDS TWO HEADED ARROW - return rune(0x21a1), true - case "Dashv": // VERTICAL BAR DOUBLE LEFT TURNSTILE - return rune(0x2ae4), true - case "Dcaron": // LATIN CAPITAL LETTER D WITH CARON - return rune(0x010e), true - case "Dcy": // CYRILLIC CAPITAL LETTER DE - return rune(0x0414), true - case "Del": // NABLA - return rune(0x2207), true - case "Delta": // GREEK CAPITAL LETTER DELTA - return rune(0x0394), true - case "Dfr": // MATHEMATICAL FRAKTUR CAPITAL D - return rune(0x01d507), true - case "Dgr": // GREEK CAPITAL LETTER DELTA - return rune(0x0394), true - case "DiacriticalAcute": // ACUTE ACCENT - return rune(0xb4), true - case "DiacriticalDot": // DOT ABOVE - return rune(0x02d9), true - case "DiacriticalDoubleAcute": // DOUBLE ACUTE ACCENT - return rune(0x02dd), true - case "DiacriticalGrave": // GRAVE ACCENT - return rune(0x60), true - case "DiacriticalTilde": // SMALL TILDE - return rune(0x02dc), true - case "Diamond": // DIAMOND OPERATOR - return rune(0x22c4), true - case "DifferentialD": // DOUBLE-STRUCK ITALIC SMALL D - return rune(0x2146), true - case "Dopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL D - return rune(0x01d53b), true - case "Dot": // DIAERESIS - return rune(0xa8), true - case "DotDot": // COMBINING FOUR DOTS ABOVE - return rune(0x20dc), true - case "DotEqual": // APPROACHES THE LIMIT - return rune(0x2250), true - case "DoubleContourIntegral": // SURFACE INTEGRAL - return rune(0x222f), true - case "DoubleDot": // DIAERESIS - return rune(0xa8), true - case "DoubleDownArrow": // DOWNWARDS DOUBLE ARROW - return rune(0x21d3), true - case "DoubleLeftArrow": // LEFTWARDS DOUBLE ARROW - return rune(0x21d0), true - case "DoubleLeftRightArrow": // LEFT RIGHT DOUBLE ARROW - return rune(0x21d4), true - case "DoubleLeftTee": // VERTICAL BAR DOUBLE LEFT TURNSTILE - return rune(0x2ae4), true - case "DoubleLongLeftArrow": // LONG LEFTWARDS DOUBLE ARROW - return rune(0x27f8), true - case "DoubleLongLeftRightArrow": // LONG LEFT RIGHT DOUBLE ARROW - return rune(0x27fa), true - case "DoubleLongRightArrow": // LONG RIGHTWARDS DOUBLE ARROW - return rune(0x27f9), true - case "DoubleRightArrow": // RIGHTWARDS DOUBLE ARROW - return rune(0x21d2), true - case "DoubleRightTee": // TRUE - return rune(0x22a8), true - case "DoubleUpArrow": // UPWARDS DOUBLE ARROW - return rune(0x21d1), true - case "DoubleUpDownArrow": // UP DOWN DOUBLE ARROW - return rune(0x21d5), true - case "DoubleVerticalBar": // PARALLEL TO - return rune(0x2225), true - case "DownArrow": // DOWNWARDS ARROW - return rune(0x2193), true - case "DownArrowBar": // DOWNWARDS ARROW TO BAR - return rune(0x2913), true - case "DownArrowUpArrow": // DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW - return rune(0x21f5), true - case "DownBreve": // COMBINING INVERTED BREVE - return rune(0x0311), true - case "DownLeftRightVector": // LEFT BARB DOWN RIGHT BARB DOWN HARPOON - return rune(0x2950), true - case "DownLeftTeeVector": // LEFTWARDS HARPOON WITH BARB DOWN FROM BAR - return rune(0x295e), true - case "DownLeftVector": // LEFTWARDS HARPOON WITH BARB DOWNWARDS - return rune(0x21bd), true - case "DownLeftVectorBar": // LEFTWARDS HARPOON WITH BARB DOWN TO BAR - return rune(0x2956), true - case "DownRightTeeVector": // RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR - return rune(0x295f), true - case "DownRightVector": // RIGHTWARDS HARPOON WITH BARB DOWNWARDS - return rune(0x21c1), true - case "DownRightVectorBar": // RIGHTWARDS HARPOON WITH BARB DOWN TO BAR - return rune(0x2957), true - case "DownTee": // DOWN TACK - return rune(0x22a4), true - case "DownTeeArrow": // DOWNWARDS ARROW FROM BAR - return rune(0x21a7), true - case "Downarrow": // DOWNWARDS DOUBLE ARROW - return rune(0x21d3), true - case "Dscr": // MATHEMATICAL SCRIPT CAPITAL D - return rune(0x01d49f), true - case "Dstrok": // LATIN CAPITAL LETTER D WITH STROKE - return rune(0x0110), true + case "DD": // DOUBLE-STRUCK ITALIC CAPITAL D + return rune(0x2145), true + case "DDotrahd": // RIGHTWARDS ARROW WITH DOTTED STEM + return rune(0x2911), true + case "DJcy": // CYRILLIC CAPITAL LETTER DJE + return rune(0x0402), true + case "DScy": // CYRILLIC CAPITAL LETTER DZE + return rune(0x0405), true + case "DZcy": // CYRILLIC CAPITAL LETTER DZHE + return rune(0x040f), true + case "Dagger": // DOUBLE DAGGER + return rune(0x2021), true + case "Darr": // DOWNWARDS TWO HEADED ARROW + return rune(0x21a1), true + case "Dashv": // VERTICAL BAR DOUBLE LEFT TURNSTILE + return rune(0x2ae4), true + case "Dcaron": // LATIN CAPITAL LETTER D WITH CARON + return rune(0x010e), true + case "Dcy": // CYRILLIC CAPITAL LETTER DE + return rune(0x0414), true + case "Del": // NABLA + return rune(0x2207), true + case "Delta": // GREEK CAPITAL LETTER DELTA + return rune(0x0394), true + case "Dfr": // MATHEMATICAL FRAKTUR CAPITAL D + return rune(0x01d507), true + case "Dgr": // GREEK CAPITAL LETTER DELTA + return rune(0x0394), true + case "DiacriticalAcute": // ACUTE ACCENT + return rune(0xb4), true + case "DiacriticalDot": // DOT ABOVE + return rune(0x02d9), true + case "DiacriticalDoubleAcute": // DOUBLE ACUTE ACCENT + return rune(0x02dd), true + case "DiacriticalGrave": // GRAVE ACCENT + return rune(0x60), true + case "DiacriticalTilde": // SMALL TILDE + return rune(0x02dc), true + case "Diamond": // DIAMOND OPERATOR + return rune(0x22c4), true + case "DifferentialD": // DOUBLE-STRUCK ITALIC SMALL D + return rune(0x2146), true + case "Dopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL D + return rune(0x01d53b), true + case "Dot": // DIAERESIS + return rune(0xa8), true + case "DotDot": // COMBINING FOUR DOTS ABOVE + return rune(0x20dc), true + case "DotEqual": // APPROACHES THE LIMIT + return rune(0x2250), true + case "DoubleContourIntegral": // SURFACE INTEGRAL + return rune(0x222f), true + case "DoubleDot": // DIAERESIS + return rune(0xa8), true + case "DoubleDownArrow": // DOWNWARDS DOUBLE ARROW + return rune(0x21d3), true + case "DoubleLeftArrow": // LEFTWARDS DOUBLE ARROW + return rune(0x21d0), true + case "DoubleLeftRightArrow": // LEFT RIGHT DOUBLE ARROW + return rune(0x21d4), true + case "DoubleLeftTee": // VERTICAL BAR DOUBLE LEFT TURNSTILE + return rune(0x2ae4), true + case "DoubleLongLeftArrow": // LONG LEFTWARDS DOUBLE ARROW + return rune(0x27f8), true + case "DoubleLongLeftRightArrow": // LONG LEFT RIGHT DOUBLE ARROW + return rune(0x27fa), true + case "DoubleLongRightArrow": // LONG RIGHTWARDS DOUBLE ARROW + return rune(0x27f9), true + case "DoubleRightArrow": // RIGHTWARDS DOUBLE ARROW + return rune(0x21d2), true + case "DoubleRightTee": // TRUE + return rune(0x22a8), true + case "DoubleUpArrow": // UPWARDS DOUBLE ARROW + return rune(0x21d1), true + case "DoubleUpDownArrow": // UP DOWN DOUBLE ARROW + return rune(0x21d5), true + case "DoubleVerticalBar": // PARALLEL TO + return rune(0x2225), true + case "DownArrow": // DOWNWARDS ARROW + return rune(0x2193), true + case "DownArrowBar": // DOWNWARDS ARROW TO BAR + return rune(0x2913), true + case "DownArrowUpArrow": // DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW + return rune(0x21f5), true + case "DownBreve": // COMBINING INVERTED BREVE + return rune(0x0311), true + case "DownLeftRightVector": // LEFT BARB DOWN RIGHT BARB DOWN HARPOON + return rune(0x2950), true + case "DownLeftTeeVector": // LEFTWARDS HARPOON WITH BARB DOWN FROM BAR + return rune(0x295e), true + case "DownLeftVector": // LEFTWARDS HARPOON WITH BARB DOWNWARDS + return rune(0x21bd), true + case "DownLeftVectorBar": // LEFTWARDS HARPOON WITH BARB DOWN TO BAR + return rune(0x2956), true + case "DownRightTeeVector": // RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR + return rune(0x295f), true + case "DownRightVector": // RIGHTWARDS HARPOON WITH BARB DOWNWARDS + return rune(0x21c1), true + case "DownRightVectorBar": // RIGHTWARDS HARPOON WITH BARB DOWN TO BAR + return rune(0x2957), true + case "DownTee": // DOWN TACK + return rune(0x22a4), true + case "DownTeeArrow": // DOWNWARDS ARROW FROM BAR + return rune(0x21a7), true + case "Downarrow": // DOWNWARDS DOUBLE ARROW + return rune(0x21d3), true + case "Dscr": // MATHEMATICAL SCRIPT CAPITAL D + return rune(0x01d49f), true + case "Dstrok": // LATIN CAPITAL LETTER D WITH STROKE + return rune(0x0110), true } case 'E': switch name { - case "EEacgr": // GREEK CAPITAL LETTER ETA WITH TONOS - return rune(0x0389), true - case "EEgr": // GREEK CAPITAL LETTER ETA - return rune(0x0397), true - case "ENG": // LATIN CAPITAL LETTER ENG - return rune(0x014a), true - case "ETH": // LATIN CAPITAL LETTER ETH - return rune(0xd0), true - case "Eacgr": // GREEK CAPITAL LETTER EPSILON WITH TONOS - return rune(0x0388), true - case "Eacute": // LATIN CAPITAL LETTER E WITH ACUTE - return rune(0xc9), true - case "Ecaron": // LATIN CAPITAL LETTER E WITH CARON - return rune(0x011a), true - case "Ecirc": // LATIN CAPITAL LETTER E WITH CIRCUMFLEX - return rune(0xca), true - case "Ecy": // CYRILLIC CAPITAL LETTER E - return rune(0x042d), true - case "Edot": // LATIN CAPITAL LETTER E WITH DOT ABOVE - return rune(0x0116), true - case "Efr": // MATHEMATICAL FRAKTUR CAPITAL E - return rune(0x01d508), true - case "Egr": // GREEK CAPITAL LETTER EPSILON - return rune(0x0395), true - case "Egrave": // LATIN CAPITAL LETTER E WITH GRAVE - return rune(0xc8), true - case "Element": // ELEMENT OF - return rune(0x2208), true - case "Emacr": // LATIN CAPITAL LETTER E WITH MACRON - return rune(0x0112), true - case "EmptySmallSquare": // WHITE MEDIUM SQUARE - return rune(0x25fb), true - case "EmptyVerySmallSquare": // WHITE SMALL SQUARE - return rune(0x25ab), true - case "Eogon": // LATIN CAPITAL LETTER E WITH OGONEK - return rune(0x0118), true - case "Eopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL E - return rune(0x01d53c), true - case "Epsilon": // GREEK CAPITAL LETTER EPSILON - return rune(0x0395), true - case "Equal": // TWO CONSECUTIVE EQUALS SIGNS - return rune(0x2a75), true - case "EqualTilde": // MINUS TILDE - return rune(0x2242), true - case "Equilibrium": // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON - return rune(0x21cc), true - case "Escr": // SCRIPT CAPITAL E - return rune(0x2130), true - case "Esim": // EQUALS SIGN ABOVE TILDE OPERATOR - return rune(0x2a73), true - case "Eta": // GREEK CAPITAL LETTER ETA - return rune(0x0397), true - case "Euml": // LATIN CAPITAL LETTER E WITH DIAERESIS - return rune(0xcb), true - case "Exists": // THERE EXISTS - return rune(0x2203), true - case "ExponentialE": // DOUBLE-STRUCK ITALIC SMALL E - return rune(0x2147), true + case "EEacgr": // GREEK CAPITAL LETTER ETA WITH TONOS + return rune(0x0389), true + case "EEgr": // GREEK CAPITAL LETTER ETA + return rune(0x0397), true + case "ENG": // LATIN CAPITAL LETTER ENG + return rune(0x014a), true + case "ETH": // LATIN CAPITAL LETTER ETH + return rune(0xd0), true + case "Eacgr": // GREEK CAPITAL LETTER EPSILON WITH TONOS + return rune(0x0388), true + case "Eacute": // LATIN CAPITAL LETTER E WITH ACUTE + return rune(0xc9), true + case "Ecaron": // LATIN CAPITAL LETTER E WITH CARON + return rune(0x011a), true + case "Ecirc": // LATIN CAPITAL LETTER E WITH CIRCUMFLEX + return rune(0xca), true + case "Ecy": // CYRILLIC CAPITAL LETTER E + return rune(0x042d), true + case "Edot": // LATIN CAPITAL LETTER E WITH DOT ABOVE + return rune(0x0116), true + case "Efr": // MATHEMATICAL FRAKTUR CAPITAL E + return rune(0x01d508), true + case "Egr": // GREEK CAPITAL LETTER EPSILON + return rune(0x0395), true + case "Egrave": // LATIN CAPITAL LETTER E WITH GRAVE + return rune(0xc8), true + case "Element": // ELEMENT OF + return rune(0x2208), true + case "Emacr": // LATIN CAPITAL LETTER E WITH MACRON + return rune(0x0112), true + case "EmptySmallSquare": // WHITE MEDIUM SQUARE + return rune(0x25fb), true + case "EmptyVerySmallSquare": // WHITE SMALL SQUARE + return rune(0x25ab), true + case "Eogon": // LATIN CAPITAL LETTER E WITH OGONEK + return rune(0x0118), true + case "Eopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL E + return rune(0x01d53c), true + case "Epsilon": // GREEK CAPITAL LETTER EPSILON + return rune(0x0395), true + case "Equal": // TWO CONSECUTIVE EQUALS SIGNS + return rune(0x2a75), true + case "EqualTilde": // MINUS TILDE + return rune(0x2242), true + case "Equilibrium": // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON + return rune(0x21cc), true + case "Escr": // SCRIPT CAPITAL E + return rune(0x2130), true + case "Esim": // EQUALS SIGN ABOVE TILDE OPERATOR + return rune(0x2a73), true + case "Eta": // GREEK CAPITAL LETTER ETA + return rune(0x0397), true + case "Euml": // LATIN CAPITAL LETTER E WITH DIAERESIS + return rune(0xcb), true + case "Exists": // THERE EXISTS + return rune(0x2203), true + case "ExponentialE": // DOUBLE-STRUCK ITALIC SMALL E + return rune(0x2147), true } case 'F': switch name { - case "Fcy": // CYRILLIC CAPITAL LETTER EF - return rune(0x0424), true - case "Ffr": // MATHEMATICAL FRAKTUR CAPITAL F - return rune(0x01d509), true - case "FilledSmallSquare": // BLACK MEDIUM SQUARE - return rune(0x25fc), true - case "FilledVerySmallSquare": // BLACK SMALL SQUARE - return rune(0x25aa), true - case "Fopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL F - return rune(0x01d53d), true - case "ForAll": // FOR ALL - return rune(0x2200), true - case "Fouriertrf": // SCRIPT CAPITAL F - return rune(0x2131), true - case "Fscr": // SCRIPT CAPITAL F - return rune(0x2131), true + case "Fcy": // CYRILLIC CAPITAL LETTER EF + return rune(0x0424), true + case "Ffr": // MATHEMATICAL FRAKTUR CAPITAL F + return rune(0x01d509), true + case "FilledSmallSquare": // BLACK MEDIUM SQUARE + return rune(0x25fc), true + case "FilledVerySmallSquare": // BLACK SMALL SQUARE + return rune(0x25aa), true + case "Fopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL F + return rune(0x01d53d), true + case "ForAll": // FOR ALL + return rune(0x2200), true + case "Fouriertrf": // SCRIPT CAPITAL F + return rune(0x2131), true + case "Fscr": // SCRIPT CAPITAL F + return rune(0x2131), true } case 'G': switch name { - case "GJcy": // CYRILLIC CAPITAL LETTER GJE - return rune(0x0403), true - case "GT": // GREATER-THAN SIGN - return rune(0x3e), true - case "Game": // TURNED SANS-SERIF CAPITAL G - return rune(0x2141), true - case "Gamma": // GREEK CAPITAL LETTER GAMMA - return rune(0x0393), true - case "Gammad": // GREEK LETTER DIGAMMA - return rune(0x03dc), true - case "Gbreve": // LATIN CAPITAL LETTER G WITH BREVE - return rune(0x011e), true - case "Gcedil": // LATIN CAPITAL LETTER G WITH CEDILLA - return rune(0x0122), true - case "Gcirc": // LATIN CAPITAL LETTER G WITH CIRCUMFLEX - return rune(0x011c), true - case "Gcy": // CYRILLIC CAPITAL LETTER GHE - return rune(0x0413), true - case "Gdot": // LATIN CAPITAL LETTER G WITH DOT ABOVE - return rune(0x0120), true - case "Gfr": // MATHEMATICAL FRAKTUR CAPITAL G - return rune(0x01d50a), true - case "Gg": // VERY MUCH GREATER-THAN - return rune(0x22d9), true - case "Ggr": // GREEK CAPITAL LETTER GAMMA - return rune(0x0393), true - case "Gopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL G - return rune(0x01d53e), true - case "GreaterEqual": // GREATER-THAN OR EQUAL TO - return rune(0x2265), true - case "GreaterEqualLess": // GREATER-THAN EQUAL TO OR LESS-THAN - return rune(0x22db), true - case "GreaterFullEqual": // GREATER-THAN OVER EQUAL TO - return rune(0x2267), true - case "GreaterGreater": // DOUBLE NESTED GREATER-THAN - return rune(0x2aa2), true - case "GreaterLess": // GREATER-THAN OR LESS-THAN - return rune(0x2277), true - case "GreaterSlantEqual": // GREATER-THAN OR SLANTED EQUAL TO - return rune(0x2a7e), true - case "GreaterTilde": // GREATER-THAN OR EQUIVALENT TO - return rune(0x2273), true - case "Gscr": // MATHEMATICAL SCRIPT CAPITAL G - return rune(0x01d4a2), true - case "Gt": // MUCH GREATER-THAN - return rune(0x226b), true + case "GJcy": // CYRILLIC CAPITAL LETTER GJE + return rune(0x0403), true + case "GT": // GREATER-THAN SIGN + return rune(0x3e), true + case "Game": // TURNED SANS-SERIF CAPITAL G + return rune(0x2141), true + case "Gamma": // GREEK CAPITAL LETTER GAMMA + return rune(0x0393), true + case "Gammad": // GREEK LETTER DIGAMMA + return rune(0x03dc), true + case "Gbreve": // LATIN CAPITAL LETTER G WITH BREVE + return rune(0x011e), true + case "Gcedil": // LATIN CAPITAL LETTER G WITH CEDILLA + return rune(0x0122), true + case "Gcirc": // LATIN CAPITAL LETTER G WITH CIRCUMFLEX + return rune(0x011c), true + case "Gcy": // CYRILLIC CAPITAL LETTER GHE + return rune(0x0413), true + case "Gdot": // LATIN CAPITAL LETTER G WITH DOT ABOVE + return rune(0x0120), true + case "Gfr": // MATHEMATICAL FRAKTUR CAPITAL G + return rune(0x01d50a), true + case "Gg": // VERY MUCH GREATER-THAN + return rune(0x22d9), true + case "Ggr": // GREEK CAPITAL LETTER GAMMA + return rune(0x0393), true + case "Gopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL G + return rune(0x01d53e), true + case "GreaterEqual": // GREATER-THAN OR EQUAL TO + return rune(0x2265), true + case "GreaterEqualLess": // GREATER-THAN EQUAL TO OR LESS-THAN + return rune(0x22db), true + case "GreaterFullEqual": // GREATER-THAN OVER EQUAL TO + return rune(0x2267), true + case "GreaterGreater": // DOUBLE NESTED GREATER-THAN + return rune(0x2aa2), true + case "GreaterLess": // GREATER-THAN OR LESS-THAN + return rune(0x2277), true + case "GreaterSlantEqual": // GREATER-THAN OR SLANTED EQUAL TO + return rune(0x2a7e), true + case "GreaterTilde": // GREATER-THAN OR EQUIVALENT TO + return rune(0x2273), true + case "Gscr": // MATHEMATICAL SCRIPT CAPITAL G + return rune(0x01d4a2), true + case "Gt": // MUCH GREATER-THAN + return rune(0x226b), true } case 'H': switch name { - case "HARDcy": // CYRILLIC CAPITAL LETTER HARD SIGN - return rune(0x042a), true - case "Hacek": // CARON - return rune(0x02c7), true - case "Hat": // CIRCUMFLEX ACCENT - return rune(0x5e), true - case "Hcirc": // LATIN CAPITAL LETTER H WITH CIRCUMFLEX - return rune(0x0124), true - case "Hfr": // BLACK-LETTER CAPITAL H - return rune(0x210c), true - case "HilbertSpace": // SCRIPT CAPITAL H - return rune(0x210b), true - case "Hopf": // DOUBLE-STRUCK CAPITAL H - return rune(0x210d), true - case "HorizontalLine": // BOX DRAWINGS LIGHT HORIZONTAL - return rune(0x2500), true - case "Hscr": // SCRIPT CAPITAL H - return rune(0x210b), true - case "Hstrok": // LATIN CAPITAL LETTER H WITH STROKE - return rune(0x0126), true - case "HumpDownHump": // GEOMETRICALLY EQUIVALENT TO - return rune(0x224e), true - case "HumpEqual": // DIFFERENCE BETWEEN - return rune(0x224f), true + case "HARDcy": // CYRILLIC CAPITAL LETTER HARD SIGN + return rune(0x042a), true + case "Hacek": // CARON + return rune(0x02c7), true + case "Hat": // CIRCUMFLEX ACCENT + return rune(0x5e), true + case "Hcirc": // LATIN CAPITAL LETTER H WITH CIRCUMFLEX + return rune(0x0124), true + case "Hfr": // BLACK-LETTER CAPITAL H + return rune(0x210c), true + case "HilbertSpace": // SCRIPT CAPITAL H + return rune(0x210b), true + case "Hopf": // DOUBLE-STRUCK CAPITAL H + return rune(0x210d), true + case "HorizontalLine": // BOX DRAWINGS LIGHT HORIZONTAL + return rune(0x2500), true + case "Hscr": // SCRIPT CAPITAL H + return rune(0x210b), true + case "Hstrok": // LATIN CAPITAL LETTER H WITH STROKE + return rune(0x0126), true + case "HumpDownHump": // GEOMETRICALLY EQUIVALENT TO + return rune(0x224e), true + case "HumpEqual": // DIFFERENCE BETWEEN + return rune(0x224f), true } case 'I': switch name { - case "IEcy": // CYRILLIC CAPITAL LETTER IE - return rune(0x0415), true - case "IJlig": // LATIN CAPITAL LIGATURE IJ - return rune(0x0132), true - case "IOcy": // CYRILLIC CAPITAL LETTER IO - return rune(0x0401), true - case "Iacgr": // GREEK CAPITAL LETTER IOTA WITH TONOS - return rune(0x038a), true - case "Iacute": // LATIN CAPITAL LETTER I WITH ACUTE - return rune(0xcd), true - case "Icirc": // LATIN CAPITAL LETTER I WITH CIRCUMFLEX - return rune(0xce), true - case "Icy": // CYRILLIC CAPITAL LETTER I - return rune(0x0418), true - case "Idigr": // GREEK CAPITAL LETTER IOTA WITH DIALYTIKA - return rune(0x03aa), true - case "Idot": // LATIN CAPITAL LETTER I WITH DOT ABOVE - return rune(0x0130), true - case "Ifr": // BLACK-LETTER CAPITAL I - return rune(0x2111), true - case "Igr": // GREEK CAPITAL LETTER IOTA - return rune(0x0399), true - case "Igrave": // LATIN CAPITAL LETTER I WITH GRAVE - return rune(0xcc), true - case "Im": // BLACK-LETTER CAPITAL I - return rune(0x2111), true - case "Imacr": // LATIN CAPITAL LETTER I WITH MACRON - return rune(0x012a), true - case "ImaginaryI": // DOUBLE-STRUCK ITALIC SMALL I - return rune(0x2148), true - case "Implies": // RIGHTWARDS DOUBLE ARROW - return rune(0x21d2), true - case "Int": // DOUBLE INTEGRAL - return rune(0x222c), true - case "Integral": // INTEGRAL - return rune(0x222b), true - case "Intersection": // N-ARY INTERSECTION - return rune(0x22c2), true - case "InvisibleComma": // INVISIBLE SEPARATOR - return rune(0x2063), true - case "InvisibleTimes": // INVISIBLE TIMES - return rune(0x2062), true - case "Iogon": // LATIN CAPITAL LETTER I WITH OGONEK - return rune(0x012e), true - case "Iopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL I - return rune(0x01d540), true - case "Iota": // GREEK CAPITAL LETTER IOTA - return rune(0x0399), true - case "Iscr": // SCRIPT CAPITAL I - return rune(0x2110), true - case "Itilde": // LATIN CAPITAL LETTER I WITH TILDE - return rune(0x0128), true - case "Iukcy": // CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I - return rune(0x0406), true - case "Iuml": // LATIN CAPITAL LETTER I WITH DIAERESIS - return rune(0xcf), true + case "IEcy": // CYRILLIC CAPITAL LETTER IE + return rune(0x0415), true + case "IJlig": // LATIN CAPITAL LIGATURE IJ + return rune(0x0132), true + case "IOcy": // CYRILLIC CAPITAL LETTER IO + return rune(0x0401), true + case "Iacgr": // GREEK CAPITAL LETTER IOTA WITH TONOS + return rune(0x038a), true + case "Iacute": // LATIN CAPITAL LETTER I WITH ACUTE + return rune(0xcd), true + case "Icirc": // LATIN CAPITAL LETTER I WITH CIRCUMFLEX + return rune(0xce), true + case "Icy": // CYRILLIC CAPITAL LETTER I + return rune(0x0418), true + case "Idigr": // GREEK CAPITAL LETTER IOTA WITH DIALYTIKA + return rune(0x03aa), true + case "Idot": // LATIN CAPITAL LETTER I WITH DOT ABOVE + return rune(0x0130), true + case "Ifr": // BLACK-LETTER CAPITAL I + return rune(0x2111), true + case "Igr": // GREEK CAPITAL LETTER IOTA + return rune(0x0399), true + case "Igrave": // LATIN CAPITAL LETTER I WITH GRAVE + return rune(0xcc), true + case "Im": // BLACK-LETTER CAPITAL I + return rune(0x2111), true + case "Imacr": // LATIN CAPITAL LETTER I WITH MACRON + return rune(0x012a), true + case "ImaginaryI": // DOUBLE-STRUCK ITALIC SMALL I + return rune(0x2148), true + case "Implies": // RIGHTWARDS DOUBLE ARROW + return rune(0x21d2), true + case "Int": // DOUBLE INTEGRAL + return rune(0x222c), true + case "Integral": // INTEGRAL + return rune(0x222b), true + case "Intersection": // N-ARY INTERSECTION + return rune(0x22c2), true + case "InvisibleComma": // INVISIBLE SEPARATOR + return rune(0x2063), true + case "InvisibleTimes": // INVISIBLE TIMES + return rune(0x2062), true + case "Iogon": // LATIN CAPITAL LETTER I WITH OGONEK + return rune(0x012e), true + case "Iopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL I + return rune(0x01d540), true + case "Iota": // GREEK CAPITAL LETTER IOTA + return rune(0x0399), true + case "Iscr": // SCRIPT CAPITAL I + return rune(0x2110), true + case "Itilde": // LATIN CAPITAL LETTER I WITH TILDE + return rune(0x0128), true + case "Iukcy": // CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I + return rune(0x0406), true + case "Iuml": // LATIN CAPITAL LETTER I WITH DIAERESIS + return rune(0xcf), true } case 'J': switch name { - case "Jcirc": // LATIN CAPITAL LETTER J WITH CIRCUMFLEX - return rune(0x0134), true - case "Jcy": // CYRILLIC CAPITAL LETTER SHORT I - return rune(0x0419), true - case "Jfr": // MATHEMATICAL FRAKTUR CAPITAL J - return rune(0x01d50d), true - case "Jopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL J - return rune(0x01d541), true - case "Jscr": // MATHEMATICAL SCRIPT CAPITAL J - return rune(0x01d4a5), true - case "Jsercy": // CYRILLIC CAPITAL LETTER JE - return rune(0x0408), true - case "Jukcy": // CYRILLIC CAPITAL LETTER UKRAINIAN IE - return rune(0x0404), true + case "Jcirc": // LATIN CAPITAL LETTER J WITH CIRCUMFLEX + return rune(0x0134), true + case "Jcy": // CYRILLIC CAPITAL LETTER SHORT I + return rune(0x0419), true + case "Jfr": // MATHEMATICAL FRAKTUR CAPITAL J + return rune(0x01d50d), true + case "Jopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL J + return rune(0x01d541), true + case "Jscr": // MATHEMATICAL SCRIPT CAPITAL J + return rune(0x01d4a5), true + case "Jsercy": // CYRILLIC CAPITAL LETTER JE + return rune(0x0408), true + case "Jukcy": // CYRILLIC CAPITAL LETTER UKRAINIAN IE + return rune(0x0404), true } case 'K': switch name { - case "KHcy": // CYRILLIC CAPITAL LETTER HA - return rune(0x0425), true - case "KHgr": // GREEK CAPITAL LETTER CHI - return rune(0x03a7), true - case "KJcy": // CYRILLIC CAPITAL LETTER KJE - return rune(0x040c), true - case "Kappa": // GREEK CAPITAL LETTER KAPPA - return rune(0x039a), true - case "Kcedil": // LATIN CAPITAL LETTER K WITH CEDILLA - return rune(0x0136), true - case "Kcy": // CYRILLIC CAPITAL LETTER KA - return rune(0x041a), true - case "Kfr": // MATHEMATICAL FRAKTUR CAPITAL K - return rune(0x01d50e), true - case "Kgr": // GREEK CAPITAL LETTER KAPPA - return rune(0x039a), true - case "Kopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL K - return rune(0x01d542), true - case "Kscr": // MATHEMATICAL SCRIPT CAPITAL K - return rune(0x01d4a6), true + case "KHcy": // CYRILLIC CAPITAL LETTER HA + return rune(0x0425), true + case "KHgr": // GREEK CAPITAL LETTER CHI + return rune(0x03a7), true + case "KJcy": // CYRILLIC CAPITAL LETTER KJE + return rune(0x040c), true + case "Kappa": // GREEK CAPITAL LETTER KAPPA + return rune(0x039a), true + case "Kcedil": // LATIN CAPITAL LETTER K WITH CEDILLA + return rune(0x0136), true + case "Kcy": // CYRILLIC CAPITAL LETTER KA + return rune(0x041a), true + case "Kfr": // MATHEMATICAL FRAKTUR CAPITAL K + return rune(0x01d50e), true + case "Kgr": // GREEK CAPITAL LETTER KAPPA + return rune(0x039a), true + case "Kopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL K + return rune(0x01d542), true + case "Kscr": // MATHEMATICAL SCRIPT CAPITAL K + return rune(0x01d4a6), true } case 'L': switch name { - case "LJcy": // CYRILLIC CAPITAL LETTER LJE - return rune(0x0409), true - case "LT": // LESS-THAN SIGN - return rune(0x3c), true - case "Lacute": // LATIN CAPITAL LETTER L WITH ACUTE - return rune(0x0139), true - case "Lambda": // GREEK CAPITAL LETTER LAMDA - return rune(0x039b), true - case "Lang": // MATHEMATICAL LEFT DOUBLE ANGLE BRACKET - return rune(0x27ea), true - case "Laplacetrf": // SCRIPT CAPITAL L - return rune(0x2112), true - case "Larr": // LEFTWARDS TWO HEADED ARROW - return rune(0x219e), true - case "Lcaron": // LATIN CAPITAL LETTER L WITH CARON - return rune(0x013d), true - case "Lcedil": // LATIN CAPITAL LETTER L WITH CEDILLA - return rune(0x013b), true - case "Lcy": // CYRILLIC CAPITAL LETTER EL - return rune(0x041b), true - case "LeftAngleBracket": // MATHEMATICAL LEFT ANGLE BRACKET - return rune(0x27e8), true - case "LeftArrow": // LEFTWARDS ARROW - return rune(0x2190), true - case "LeftArrowBar": // LEFTWARDS ARROW TO BAR - return rune(0x21e4), true - case "LeftArrowRightArrow": // LEFTWARDS ARROW OVER RIGHTWARDS ARROW - return rune(0x21c6), true - case "LeftCeiling": // LEFT CEILING - return rune(0x2308), true - case "LeftDoubleBracket": // MATHEMATICAL LEFT WHITE SQUARE BRACKET - return rune(0x27e6), true - case "LeftDownTeeVector": // DOWNWARDS HARPOON WITH BARB LEFT FROM BAR - return rune(0x2961), true - case "LeftDownVector": // DOWNWARDS HARPOON WITH BARB LEFTWARDS - return rune(0x21c3), true - case "LeftDownVectorBar": // DOWNWARDS HARPOON WITH BARB LEFT TO BAR - return rune(0x2959), true - case "LeftFloor": // LEFT FLOOR - return rune(0x230a), true - case "LeftRightArrow": // LEFT RIGHT ARROW - return rune(0x2194), true - case "LeftRightVector": // LEFT BARB UP RIGHT BARB UP HARPOON - return rune(0x294e), true - case "LeftTee": // LEFT TACK - return rune(0x22a3), true - case "LeftTeeArrow": // LEFTWARDS ARROW FROM BAR - return rune(0x21a4), true - case "LeftTeeVector": // LEFTWARDS HARPOON WITH BARB UP FROM BAR - return rune(0x295a), true - case "LeftTriangle": // NORMAL SUBGROUP OF - return rune(0x22b2), true - case "LeftTriangleBar": // LEFT TRIANGLE BESIDE VERTICAL BAR - return rune(0x29cf), true - case "LeftTriangleEqual": // NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22b4), true - case "LeftUpDownVector": // UP BARB LEFT DOWN BARB LEFT HARPOON - return rune(0x2951), true - case "LeftUpTeeVector": // UPWARDS HARPOON WITH BARB LEFT FROM BAR - return rune(0x2960), true - case "LeftUpVector": // UPWARDS HARPOON WITH BARB LEFTWARDS - return rune(0x21bf), true - case "LeftUpVectorBar": // UPWARDS HARPOON WITH BARB LEFT TO BAR - return rune(0x2958), true - case "LeftVector": // LEFTWARDS HARPOON WITH BARB UPWARDS - return rune(0x21bc), true - case "LeftVectorBar": // LEFTWARDS HARPOON WITH BARB UP TO BAR - return rune(0x2952), true - case "Leftarrow": // LEFTWARDS DOUBLE ARROW - return rune(0x21d0), true - case "Leftrightarrow": // LEFT RIGHT DOUBLE ARROW - return rune(0x21d4), true - case "LessEqualGreater": // LESS-THAN EQUAL TO OR GREATER-THAN - return rune(0x22da), true - case "LessFullEqual": // LESS-THAN OVER EQUAL TO - return rune(0x2266), true - case "LessGreater": // LESS-THAN OR GREATER-THAN - return rune(0x2276), true - case "LessLess": // DOUBLE NESTED LESS-THAN - return rune(0x2aa1), true - case "LessSlantEqual": // LESS-THAN OR SLANTED EQUAL TO - return rune(0x2a7d), true - case "LessTilde": // LESS-THAN OR EQUIVALENT TO - return rune(0x2272), true - case "Lfr": // MATHEMATICAL FRAKTUR CAPITAL L - return rune(0x01d50f), true - case "Lgr": // GREEK CAPITAL LETTER LAMDA - return rune(0x039b), true - case "Ll": // VERY MUCH LESS-THAN - return rune(0x22d8), true - case "Lleftarrow": // LEFTWARDS TRIPLE ARROW - return rune(0x21da), true - case "Lmidot": // LATIN CAPITAL LETTER L WITH MIDDLE DOT - return rune(0x013f), true - case "LongLeftArrow": // LONG LEFTWARDS ARROW - return rune(0x27f5), true - case "LongLeftRightArrow": // LONG LEFT RIGHT ARROW - return rune(0x27f7), true - case "LongRightArrow": // LONG RIGHTWARDS ARROW - return rune(0x27f6), true - case "Longleftarrow": // LONG LEFTWARDS DOUBLE ARROW - return rune(0x27f8), true - case "Longleftrightarrow": // LONG LEFT RIGHT DOUBLE ARROW - return rune(0x27fa), true - case "Longrightarrow": // LONG RIGHTWARDS DOUBLE ARROW - return rune(0x27f9), true - case "Lopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL L - return rune(0x01d543), true - case "LowerLeftArrow": // SOUTH WEST ARROW - return rune(0x2199), true - case "LowerRightArrow": // SOUTH EAST ARROW - return rune(0x2198), true - case "Lscr": // SCRIPT CAPITAL L - return rune(0x2112), true - case "Lsh": // UPWARDS ARROW WITH TIP LEFTWARDS - return rune(0x21b0), true - case "Lstrok": // LATIN CAPITAL LETTER L WITH STROKE - return rune(0x0141), true - case "Lt": // MUCH LESS-THAN - return rune(0x226a), true - case "Ltbar": // DOUBLE NESTED LESS-THAN WITH UNDERBAR - return rune(0x2aa3), true + case "LJcy": // CYRILLIC CAPITAL LETTER LJE + return rune(0x0409), true + case "LT": // LESS-THAN SIGN + return rune(0x3c), true + case "Lacute": // LATIN CAPITAL LETTER L WITH ACUTE + return rune(0x0139), true + case "Lambda": // GREEK CAPITAL LETTER LAMDA + return rune(0x039b), true + case "Lang": // MATHEMATICAL LEFT DOUBLE ANGLE BRACKET + return rune(0x27ea), true + case "Laplacetrf": // SCRIPT CAPITAL L + return rune(0x2112), true + case "Larr": // LEFTWARDS TWO HEADED ARROW + return rune(0x219e), true + case "Lcaron": // LATIN CAPITAL LETTER L WITH CARON + return rune(0x013d), true + case "Lcedil": // LATIN CAPITAL LETTER L WITH CEDILLA + return rune(0x013b), true + case "Lcy": // CYRILLIC CAPITAL LETTER EL + return rune(0x041b), true + case "LeftAngleBracket": // MATHEMATICAL LEFT ANGLE BRACKET + return rune(0x27e8), true + case "LeftArrow": // LEFTWARDS ARROW + return rune(0x2190), true + case "LeftArrowBar": // LEFTWARDS ARROW TO BAR + return rune(0x21e4), true + case "LeftArrowRightArrow": // LEFTWARDS ARROW OVER RIGHTWARDS ARROW + return rune(0x21c6), true + case "LeftCeiling": // LEFT CEILING + return rune(0x2308), true + case "LeftDoubleBracket": // MATHEMATICAL LEFT WHITE SQUARE BRACKET + return rune(0x27e6), true + case "LeftDownTeeVector": // DOWNWARDS HARPOON WITH BARB LEFT FROM BAR + return rune(0x2961), true + case "LeftDownVector": // DOWNWARDS HARPOON WITH BARB LEFTWARDS + return rune(0x21c3), true + case "LeftDownVectorBar": // DOWNWARDS HARPOON WITH BARB LEFT TO BAR + return rune(0x2959), true + case "LeftFloor": // LEFT FLOOR + return rune(0x230a), true + case "LeftRightArrow": // LEFT RIGHT ARROW + return rune(0x2194), true + case "LeftRightVector": // LEFT BARB UP RIGHT BARB UP HARPOON + return rune(0x294e), true + case "LeftTee": // LEFT TACK + return rune(0x22a3), true + case "LeftTeeArrow": // LEFTWARDS ARROW FROM BAR + return rune(0x21a4), true + case "LeftTeeVector": // LEFTWARDS HARPOON WITH BARB UP FROM BAR + return rune(0x295a), true + case "LeftTriangle": // NORMAL SUBGROUP OF + return rune(0x22b2), true + case "LeftTriangleBar": // LEFT TRIANGLE BESIDE VERTICAL BAR + return rune(0x29cf), true + case "LeftTriangleEqual": // NORMAL SUBGROUP OF OR EQUAL TO + return rune(0x22b4), true + case "LeftUpDownVector": // UP BARB LEFT DOWN BARB LEFT HARPOON + return rune(0x2951), true + case "LeftUpTeeVector": // UPWARDS HARPOON WITH BARB LEFT FROM BAR + return rune(0x2960), true + case "LeftUpVector": // UPWARDS HARPOON WITH BARB LEFTWARDS + return rune(0x21bf), true + case "LeftUpVectorBar": // UPWARDS HARPOON WITH BARB LEFT TO BAR + return rune(0x2958), true + case "LeftVector": // LEFTWARDS HARPOON WITH BARB UPWARDS + return rune(0x21bc), true + case "LeftVectorBar": // LEFTWARDS HARPOON WITH BARB UP TO BAR + return rune(0x2952), true + case "Leftarrow": // LEFTWARDS DOUBLE ARROW + return rune(0x21d0), true + case "Leftrightarrow": // LEFT RIGHT DOUBLE ARROW + return rune(0x21d4), true + case "LessEqualGreater": // LESS-THAN EQUAL TO OR GREATER-THAN + return rune(0x22da), true + case "LessFullEqual": // LESS-THAN OVER EQUAL TO + return rune(0x2266), true + case "LessGreater": // LESS-THAN OR GREATER-THAN + return rune(0x2276), true + case "LessLess": // DOUBLE NESTED LESS-THAN + return rune(0x2aa1), true + case "LessSlantEqual": // LESS-THAN OR SLANTED EQUAL TO + return rune(0x2a7d), true + case "LessTilde": // LESS-THAN OR EQUIVALENT TO + return rune(0x2272), true + case "Lfr": // MATHEMATICAL FRAKTUR CAPITAL L + return rune(0x01d50f), true + case "Lgr": // GREEK CAPITAL LETTER LAMDA + return rune(0x039b), true + case "Ll": // VERY MUCH LESS-THAN + return rune(0x22d8), true + case "Lleftarrow": // LEFTWARDS TRIPLE ARROW + return rune(0x21da), true + case "Lmidot": // LATIN CAPITAL LETTER L WITH MIDDLE DOT + return rune(0x013f), true + case "LongLeftArrow": // LONG LEFTWARDS ARROW + return rune(0x27f5), true + case "LongLeftRightArrow": // LONG LEFT RIGHT ARROW + return rune(0x27f7), true + case "LongRightArrow": // LONG RIGHTWARDS ARROW + return rune(0x27f6), true + case "Longleftarrow": // LONG LEFTWARDS DOUBLE ARROW + return rune(0x27f8), true + case "Longleftrightarrow": // LONG LEFT RIGHT DOUBLE ARROW + return rune(0x27fa), true + case "Longrightarrow": // LONG RIGHTWARDS DOUBLE ARROW + return rune(0x27f9), true + case "Lopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL L + return rune(0x01d543), true + case "LowerLeftArrow": // SOUTH WEST ARROW + return rune(0x2199), true + case "LowerRightArrow": // SOUTH EAST ARROW + return rune(0x2198), true + case "Lscr": // SCRIPT CAPITAL L + return rune(0x2112), true + case "Lsh": // UPWARDS ARROW WITH TIP LEFTWARDS + return rune(0x21b0), true + case "Lstrok": // LATIN CAPITAL LETTER L WITH STROKE + return rune(0x0141), true + case "Lt": // MUCH LESS-THAN + return rune(0x226a), true + case "Ltbar": // DOUBLE NESTED LESS-THAN WITH UNDERBAR + return rune(0x2aa3), true } case 'M': switch name { - case "Map": // RIGHTWARDS TWO-HEADED ARROW FROM BAR - return rune(0x2905), true - case "Mapfrom": // LEFTWARDS DOUBLE ARROW FROM BAR - return rune(0x2906), true - case "Mapto": // RIGHTWARDS DOUBLE ARROW FROM BAR - return rune(0x2907), true - case "Mcy": // CYRILLIC CAPITAL LETTER EM - return rune(0x041c), true - case "MediumSpace": // MEDIUM MATHEMATICAL SPACE - return rune(0x205f), true - case "Mellintrf": // SCRIPT CAPITAL M - return rune(0x2133), true - case "Mfr": // MATHEMATICAL FRAKTUR CAPITAL M - return rune(0x01d510), true - case "Mgr": // GREEK CAPITAL LETTER MU - return rune(0x039c), true - case "MinusPlus": // MINUS-OR-PLUS SIGN - return rune(0x2213), true - case "Mopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL M - return rune(0x01d544), true - case "Mscr": // SCRIPT CAPITAL M - return rune(0x2133), true - case "Mu": // GREEK CAPITAL LETTER MU - return rune(0x039c), true + case "Map": // RIGHTWARDS TWO-HEADED ARROW FROM BAR + return rune(0x2905), true + case "Mapfrom": // LEFTWARDS DOUBLE ARROW FROM BAR + return rune(0x2906), true + case "Mapto": // RIGHTWARDS DOUBLE ARROW FROM BAR + return rune(0x2907), true + case "Mcy": // CYRILLIC CAPITAL LETTER EM + return rune(0x041c), true + case "MediumSpace": // MEDIUM MATHEMATICAL SPACE + return rune(0x205f), true + case "Mellintrf": // SCRIPT CAPITAL M + return rune(0x2133), true + case "Mfr": // MATHEMATICAL FRAKTUR CAPITAL M + return rune(0x01d510), true + case "Mgr": // GREEK CAPITAL LETTER MU + return rune(0x039c), true + case "MinusPlus": // MINUS-OR-PLUS SIGN + return rune(0x2213), true + case "Mopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL M + return rune(0x01d544), true + case "Mscr": // SCRIPT CAPITAL M + return rune(0x2133), true + case "Mu": // GREEK CAPITAL LETTER MU + return rune(0x039c), true } case 'N': switch name { - case "NJcy": // CYRILLIC CAPITAL LETTER NJE - return rune(0x040a), true - case "Nacute": // LATIN CAPITAL LETTER N WITH ACUTE - return rune(0x0143), true - case "Ncaron": // LATIN CAPITAL LETTER N WITH CARON - return rune(0x0147), true - case "Ncedil": // LATIN CAPITAL LETTER N WITH CEDILLA - return rune(0x0145), true - case "Ncy": // CYRILLIC CAPITAL LETTER EN - return rune(0x041d), true - case "NegativeMediumSpace": // ZERO WIDTH SPACE - return rune(0x200b), true - case "NegativeThickSpace": // ZERO WIDTH SPACE - return rune(0x200b), true - case "NegativeThinSpace": // ZERO WIDTH SPACE - return rune(0x200b), true - case "NegativeVeryThinSpace": // ZERO WIDTH SPACE - return rune(0x200b), true - case "NestedGreaterGreater": // MUCH GREATER-THAN - return rune(0x226b), true - case "NestedLessLess": // MUCH LESS-THAN - return rune(0x226a), true - case "NewLine": // LINE FEED (LF) - return rune(0x0a), true - case "Nfr": // MATHEMATICAL FRAKTUR CAPITAL N - return rune(0x01d511), true - case "Ngr": // GREEK CAPITAL LETTER NU - return rune(0x039d), true - case "NoBreak": // WORD JOINER - return rune(0x2060), true - case "NonBreakingSpace": // NO-BREAK SPACE - return rune(0xa0), true - case "Nopf": // DOUBLE-STRUCK CAPITAL N - return rune(0x2115), true - case "Not": // DOUBLE STROKE NOT SIGN - return rune(0x2aec), true - case "NotCongruent": // NOT IDENTICAL TO - return rune(0x2262), true - case "NotCupCap": // NOT EQUIVALENT TO - return rune(0x226d), true - case "NotDoubleVerticalBar": // NOT PARALLEL TO - return rune(0x2226), true - case "NotElement": // NOT AN ELEMENT OF - return rune(0x2209), true - case "NotEqual": // NOT EQUAL TO - return rune(0x2260), true - case "NotEqualTilde": // MINUS TILDE with slash - return rune(0x2242), true - case "NotExists": // THERE DOES NOT EXIST - return rune(0x2204), true - case "NotGreater": // NOT GREATER-THAN - return rune(0x226f), true - case "NotGreaterEqual": // NEITHER GREATER-THAN NOR EQUAL TO - return rune(0x2271), true - case "NotGreaterFullEqual": // GREATER-THAN OVER EQUAL TO with slash - return rune(0x2267), true - case "NotGreaterGreater": // MUCH GREATER THAN with slash - return rune(0x226b), true - case "NotGreaterLess": // NEITHER GREATER-THAN NOR LESS-THAN - return rune(0x2279), true - case "NotGreaterSlantEqual": // GREATER-THAN OR SLANTED EQUAL TO with slash - return rune(0x2a7e), true - case "NotGreaterTilde": // NEITHER GREATER-THAN NOR EQUIVALENT TO - return rune(0x2275), true - case "NotHumpDownHump": // GEOMETRICALLY EQUIVALENT TO with slash - return rune(0x224e), true - case "NotHumpEqual": // DIFFERENCE BETWEEN with slash - return rune(0x224f), true - case "NotLeftTriangle": // NOT NORMAL SUBGROUP OF - return rune(0x22ea), true - case "NotLeftTriangleBar": // LEFT TRIANGLE BESIDE VERTICAL BAR with slash - return rune(0x29cf), true - case "NotLeftTriangleEqual": // NOT NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22ec), true - case "NotLess": // NOT LESS-THAN - return rune(0x226e), true - case "NotLessEqual": // NEITHER LESS-THAN NOR EQUAL TO - return rune(0x2270), true - case "NotLessGreater": // NEITHER LESS-THAN NOR GREATER-THAN - return rune(0x2278), true - case "NotLessLess": // MUCH LESS THAN with slash - return rune(0x226a), true - case "NotLessSlantEqual": // LESS-THAN OR SLANTED EQUAL TO with slash - return rune(0x2a7d), true - case "NotLessTilde": // NEITHER LESS-THAN NOR EQUIVALENT TO - return rune(0x2274), true - case "NotNestedGreaterGreater": // DOUBLE NESTED GREATER-THAN with slash - return rune(0x2aa2), true - case "NotNestedLessLess": // DOUBLE NESTED LESS-THAN with slash - return rune(0x2aa1), true - case "NotPrecedes": // DOES NOT PRECEDE - return rune(0x2280), true - case "NotPrecedesEqual": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2aaf), true - case "NotPrecedesSlantEqual": // DOES NOT PRECEDE OR EQUAL - return rune(0x22e0), true - case "NotReverseElement": // DOES NOT CONTAIN AS MEMBER - return rune(0x220c), true - case "NotRightTriangle": // DOES NOT CONTAIN AS NORMAL SUBGROUP - return rune(0x22eb), true - case "NotRightTriangleBar": // VERTICAL BAR BESIDE RIGHT TRIANGLE with slash - return rune(0x29d0), true - case "NotRightTriangleEqual": // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL - return rune(0x22ed), true - case "NotSquareSubset": // SQUARE IMAGE OF with slash - return rune(0x228f), true - case "NotSquareSubsetEqual": // NOT SQUARE IMAGE OF OR EQUAL TO - return rune(0x22e2), true - case "NotSquareSuperset": // SQUARE ORIGINAL OF with slash - return rune(0x2290), true - case "NotSquareSupersetEqual": // NOT SQUARE ORIGINAL OF OR EQUAL TO - return rune(0x22e3), true - case "NotSubset": // SUBSET OF with vertical line - return rune(0x2282), true - case "NotSubsetEqual": // NEITHER A SUBSET OF NOR EQUAL TO - return rune(0x2288), true - case "NotSucceeds": // DOES NOT SUCCEED - return rune(0x2281), true - case "NotSucceedsEqual": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2ab0), true - case "NotSucceedsSlantEqual": // DOES NOT SUCCEED OR EQUAL - return rune(0x22e1), true - case "NotSucceedsTilde": // SUCCEEDS OR EQUIVALENT TO with slash - return rune(0x227f), true - case "NotSuperset": // SUPERSET OF with vertical line - return rune(0x2283), true - case "NotSupersetEqual": // NEITHER A SUPERSET OF NOR EQUAL TO - return rune(0x2289), true - case "NotTilde": // NOT TILDE - return rune(0x2241), true - case "NotTildeEqual": // NOT ASYMPTOTICALLY EQUAL TO - return rune(0x2244), true - case "NotTildeFullEqual": // NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO - return rune(0x2247), true - case "NotTildeTilde": // NOT ALMOST EQUAL TO - return rune(0x2249), true - case "NotVerticalBar": // DOES NOT DIVIDE - return rune(0x2224), true - case "Nscr": // MATHEMATICAL SCRIPT CAPITAL N - return rune(0x01d4a9), true - case "Ntilde": // LATIN CAPITAL LETTER N WITH TILDE - return rune(0xd1), true - case "Nu": // GREEK CAPITAL LETTER NU - return rune(0x039d), true + case "NJcy": // CYRILLIC CAPITAL LETTER NJE + return rune(0x040a), true + case "Nacute": // LATIN CAPITAL LETTER N WITH ACUTE + return rune(0x0143), true + case "Ncaron": // LATIN CAPITAL LETTER N WITH CARON + return rune(0x0147), true + case "Ncedil": // LATIN CAPITAL LETTER N WITH CEDILLA + return rune(0x0145), true + case "Ncy": // CYRILLIC CAPITAL LETTER EN + return rune(0x041d), true + case "NegativeMediumSpace": // ZERO WIDTH SPACE + return rune(0x200b), true + case "NegativeThickSpace": // ZERO WIDTH SPACE + return rune(0x200b), true + case "NegativeThinSpace": // ZERO WIDTH SPACE + return rune(0x200b), true + case "NegativeVeryThinSpace": // ZERO WIDTH SPACE + return rune(0x200b), true + case "NestedGreaterGreater": // MUCH GREATER-THAN + return rune(0x226b), true + case "NestedLessLess": // MUCH LESS-THAN + return rune(0x226a), true + case "NewLine": // LINE FEED (LF) + return rune(0x0a), true + case "Nfr": // MATHEMATICAL FRAKTUR CAPITAL N + return rune(0x01d511), true + case "Ngr": // GREEK CAPITAL LETTER NU + return rune(0x039d), true + case "NoBreak": // WORD JOINER + return rune(0x2060), true + case "NonBreakingSpace": // NO-BREAK SPACE + return rune(0xa0), true + case "Nopf": // DOUBLE-STRUCK CAPITAL N + return rune(0x2115), true + case "Not": // DOUBLE STROKE NOT SIGN + return rune(0x2aec), true + case "NotCongruent": // NOT IDENTICAL TO + return rune(0x2262), true + case "NotCupCap": // NOT EQUIVALENT TO + return rune(0x226d), true + case "NotDoubleVerticalBar": // NOT PARALLEL TO + return rune(0x2226), true + case "NotElement": // NOT AN ELEMENT OF + return rune(0x2209), true + case "NotEqual": // NOT EQUAL TO + return rune(0x2260), true + case "NotEqualTilde": // MINUS TILDE with slash + return rune(0x2242), true + case "NotExists": // THERE DOES NOT EXIST + return rune(0x2204), true + case "NotGreater": // NOT GREATER-THAN + return rune(0x226f), true + case "NotGreaterEqual": // NEITHER GREATER-THAN NOR EQUAL TO + return rune(0x2271), true + case "NotGreaterFullEqual": // GREATER-THAN OVER EQUAL TO with slash + return rune(0x2267), true + case "NotGreaterGreater": // MUCH GREATER THAN with slash + return rune(0x226b), true + case "NotGreaterLess": // NEITHER GREATER-THAN NOR LESS-THAN + return rune(0x2279), true + case "NotGreaterSlantEqual": // GREATER-THAN OR SLANTED EQUAL TO with slash + return rune(0x2a7e), true + case "NotGreaterTilde": // NEITHER GREATER-THAN NOR EQUIVALENT TO + return rune(0x2275), true + case "NotHumpDownHump": // GEOMETRICALLY EQUIVALENT TO with slash + return rune(0x224e), true + case "NotHumpEqual": // DIFFERENCE BETWEEN with slash + return rune(0x224f), true + case "NotLeftTriangle": // NOT NORMAL SUBGROUP OF + return rune(0x22ea), true + case "NotLeftTriangleBar": // LEFT TRIANGLE BESIDE VERTICAL BAR with slash + return rune(0x29cf), true + case "NotLeftTriangleEqual": // NOT NORMAL SUBGROUP OF OR EQUAL TO + return rune(0x22ec), true + case "NotLess": // NOT LESS-THAN + return rune(0x226e), true + case "NotLessEqual": // NEITHER LESS-THAN NOR EQUAL TO + return rune(0x2270), true + case "NotLessGreater": // NEITHER LESS-THAN NOR GREATER-THAN + return rune(0x2278), true + case "NotLessLess": // MUCH LESS THAN with slash + return rune(0x226a), true + case "NotLessSlantEqual": // LESS-THAN OR SLANTED EQUAL TO with slash + return rune(0x2a7d), true + case "NotLessTilde": // NEITHER LESS-THAN NOR EQUIVALENT TO + return rune(0x2274), true + case "NotNestedGreaterGreater": // DOUBLE NESTED GREATER-THAN with slash + return rune(0x2aa2), true + case "NotNestedLessLess": // DOUBLE NESTED LESS-THAN with slash + return rune(0x2aa1), true + case "NotPrecedes": // DOES NOT PRECEDE + return rune(0x2280), true + case "NotPrecedesEqual": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash + return rune(0x2aaf), true + case "NotPrecedesSlantEqual": // DOES NOT PRECEDE OR EQUAL + return rune(0x22e0), true + case "NotReverseElement": // DOES NOT CONTAIN AS MEMBER + return rune(0x220c), true + case "NotRightTriangle": // DOES NOT CONTAIN AS NORMAL SUBGROUP + return rune(0x22eb), true + case "NotRightTriangleBar": // VERTICAL BAR BESIDE RIGHT TRIANGLE with slash + return rune(0x29d0), true + case "NotRightTriangleEqual": // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL + return rune(0x22ed), true + case "NotSquareSubset": // SQUARE IMAGE OF with slash + return rune(0x228f), true + case "NotSquareSubsetEqual": // NOT SQUARE IMAGE OF OR EQUAL TO + return rune(0x22e2), true + case "NotSquareSuperset": // SQUARE ORIGINAL OF with slash + return rune(0x2290), true + case "NotSquareSupersetEqual": // NOT SQUARE ORIGINAL OF OR EQUAL TO + return rune(0x22e3), true + case "NotSubset": // SUBSET OF with vertical line + return rune(0x2282), true + case "NotSubsetEqual": // NEITHER A SUBSET OF NOR EQUAL TO + return rune(0x2288), true + case "NotSucceeds": // DOES NOT SUCCEED + return rune(0x2281), true + case "NotSucceedsEqual": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash + return rune(0x2ab0), true + case "NotSucceedsSlantEqual": // DOES NOT SUCCEED OR EQUAL + return rune(0x22e1), true + case "NotSucceedsTilde": // SUCCEEDS OR EQUIVALENT TO with slash + return rune(0x227f), true + case "NotSuperset": // SUPERSET OF with vertical line + return rune(0x2283), true + case "NotSupersetEqual": // NEITHER A SUPERSET OF NOR EQUAL TO + return rune(0x2289), true + case "NotTilde": // NOT TILDE + return rune(0x2241), true + case "NotTildeEqual": // NOT ASYMPTOTICALLY EQUAL TO + return rune(0x2244), true + case "NotTildeFullEqual": // NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO + return rune(0x2247), true + case "NotTildeTilde": // NOT ALMOST EQUAL TO + return rune(0x2249), true + case "NotVerticalBar": // DOES NOT DIVIDE + return rune(0x2224), true + case "Nscr": // MATHEMATICAL SCRIPT CAPITAL N + return rune(0x01d4a9), true + case "Ntilde": // LATIN CAPITAL LETTER N WITH TILDE + return rune(0xd1), true + case "Nu": // GREEK CAPITAL LETTER NU + return rune(0x039d), true } case 'O': switch name { - case "OElig": // LATIN CAPITAL LIGATURE OE - return rune(0x0152), true - case "OHacgr": // GREEK CAPITAL LETTER OMEGA WITH TONOS - return rune(0x038f), true - case "OHgr": // GREEK CAPITAL LETTER OMEGA - return rune(0x03a9), true - case "Oacgr": // GREEK CAPITAL LETTER OMICRON WITH TONOS - return rune(0x038c), true - case "Oacute": // LATIN CAPITAL LETTER O WITH ACUTE - return rune(0xd3), true - case "Ocirc": // LATIN CAPITAL LETTER O WITH CIRCUMFLEX - return rune(0xd4), true - case "Ocy": // CYRILLIC CAPITAL LETTER O - return rune(0x041e), true - case "Odblac": // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE - return rune(0x0150), true - case "Ofr": // MATHEMATICAL FRAKTUR CAPITAL O - return rune(0x01d512), true - case "Ogr": // GREEK CAPITAL LETTER OMICRON - return rune(0x039f), true - case "Ograve": // LATIN CAPITAL LETTER O WITH GRAVE - return rune(0xd2), true - case "Omacr": // LATIN CAPITAL LETTER O WITH MACRON - return rune(0x014c), true - case "Omega": // GREEK CAPITAL LETTER OMEGA - return rune(0x03a9), true - case "Omicron": // GREEK CAPITAL LETTER OMICRON - return rune(0x039f), true - case "Oopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL O - return rune(0x01d546), true - case "OpenCurlyDoubleQuote": // LEFT DOUBLE QUOTATION MARK - return rune(0x201c), true - case "OpenCurlyQuote": // LEFT SINGLE QUOTATION MARK - return rune(0x2018), true - case "Or": // DOUBLE LOGICAL OR - return rune(0x2a54), true - case "Oscr": // MATHEMATICAL SCRIPT CAPITAL O - return rune(0x01d4aa), true - case "Oslash": // LATIN CAPITAL LETTER O WITH STROKE - return rune(0xd8), true - case "Otilde": // LATIN CAPITAL LETTER O WITH TILDE - return rune(0xd5), true - case "Otimes": // MULTIPLICATION SIGN IN DOUBLE CIRCLE - return rune(0x2a37), true - case "Ouml": // LATIN CAPITAL LETTER O WITH DIAERESIS - return rune(0xd6), true - case "OverBar": // OVERLINE - return rune(0x203e), true - case "OverBrace": // TOP CURLY BRACKET - return rune(0x23de), true - case "OverBracket": // TOP SQUARE BRACKET - return rune(0x23b4), true - case "OverParenthesis": // TOP PARENTHESIS - return rune(0x23dc), true + case "OElig": // LATIN CAPITAL LIGATURE OE + return rune(0x0152), true + case "OHacgr": // GREEK CAPITAL LETTER OMEGA WITH TONOS + return rune(0x038f), true + case "OHgr": // GREEK CAPITAL LETTER OMEGA + return rune(0x03a9), true + case "Oacgr": // GREEK CAPITAL LETTER OMICRON WITH TONOS + return rune(0x038c), true + case "Oacute": // LATIN CAPITAL LETTER O WITH ACUTE + return rune(0xd3), true + case "Ocirc": // LATIN CAPITAL LETTER O WITH CIRCUMFLEX + return rune(0xd4), true + case "Ocy": // CYRILLIC CAPITAL LETTER O + return rune(0x041e), true + case "Odblac": // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE + return rune(0x0150), true + case "Ofr": // MATHEMATICAL FRAKTUR CAPITAL O + return rune(0x01d512), true + case "Ogr": // GREEK CAPITAL LETTER OMICRON + return rune(0x039f), true + case "Ograve": // LATIN CAPITAL LETTER O WITH GRAVE + return rune(0xd2), true + case "Omacr": // LATIN CAPITAL LETTER O WITH MACRON + return rune(0x014c), true + case "Omega": // GREEK CAPITAL LETTER OMEGA + return rune(0x03a9), true + case "Omicron": // GREEK CAPITAL LETTER OMICRON + return rune(0x039f), true + case "Oopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL O + return rune(0x01d546), true + case "OpenCurlyDoubleQuote": // LEFT DOUBLE QUOTATION MARK + return rune(0x201c), true + case "OpenCurlyQuote": // LEFT SINGLE QUOTATION MARK + return rune(0x2018), true + case "Or": // DOUBLE LOGICAL OR + return rune(0x2a54), true + case "Oscr": // MATHEMATICAL SCRIPT CAPITAL O + return rune(0x01d4aa), true + case "Oslash": // LATIN CAPITAL LETTER O WITH STROKE + return rune(0xd8), true + case "Otilde": // LATIN CAPITAL LETTER O WITH TILDE + return rune(0xd5), true + case "Otimes": // MULTIPLICATION SIGN IN DOUBLE CIRCLE + return rune(0x2a37), true + case "Ouml": // LATIN CAPITAL LETTER O WITH DIAERESIS + return rune(0xd6), true + case "OverBar": // OVERLINE + return rune(0x203e), true + case "OverBrace": // TOP CURLY BRACKET + return rune(0x23de), true + case "OverBracket": // TOP SQUARE BRACKET + return rune(0x23b4), true + case "OverParenthesis": // TOP PARENTHESIS + return rune(0x23dc), true } case 'P': switch name { - case "PHgr": // GREEK CAPITAL LETTER PHI - return rune(0x03a6), true - case "PSgr": // GREEK CAPITAL LETTER PSI - return rune(0x03a8), true - case "PartialD": // PARTIAL DIFFERENTIAL - return rune(0x2202), true - case "Pcy": // CYRILLIC CAPITAL LETTER PE - return rune(0x041f), true - case "Pfr": // MATHEMATICAL FRAKTUR CAPITAL P - return rune(0x01d513), true - case "Pgr": // GREEK CAPITAL LETTER PI - return rune(0x03a0), true - case "Phi": // GREEK CAPITAL LETTER PHI - return rune(0x03a6), true - case "Pi": // GREEK CAPITAL LETTER PI - return rune(0x03a0), true - case "PlusMinus": // PLUS-MINUS SIGN - return rune(0xb1), true - case "Poincareplane": // BLACK-LETTER CAPITAL H - return rune(0x210c), true - case "Popf": // DOUBLE-STRUCK CAPITAL P - return rune(0x2119), true - case "Pr": // DOUBLE PRECEDES - return rune(0x2abb), true - case "Precedes": // PRECEDES - return rune(0x227a), true - case "PrecedesEqual": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2aaf), true - case "PrecedesSlantEqual": // PRECEDES OR EQUAL TO - return rune(0x227c), true - case "PrecedesTilde": // PRECEDES OR EQUIVALENT TO - return rune(0x227e), true - case "Prime": // DOUBLE PRIME - return rune(0x2033), true - case "Product": // N-ARY PRODUCT - return rune(0x220f), true - case "Proportion": // PROPORTION - return rune(0x2237), true - case "Proportional": // PROPORTIONAL TO - return rune(0x221d), true - case "Pscr": // MATHEMATICAL SCRIPT CAPITAL P - return rune(0x01d4ab), true - case "Psi": // GREEK CAPITAL LETTER PSI - return rune(0x03a8), true + case "PHgr": // GREEK CAPITAL LETTER PHI + return rune(0x03a6), true + case "PSgr": // GREEK CAPITAL LETTER PSI + return rune(0x03a8), true + case "PartialD": // PARTIAL DIFFERENTIAL + return rune(0x2202), true + case "Pcy": // CYRILLIC CAPITAL LETTER PE + return rune(0x041f), true + case "Pfr": // MATHEMATICAL FRAKTUR CAPITAL P + return rune(0x01d513), true + case "Pgr": // GREEK CAPITAL LETTER PI + return rune(0x03a0), true + case "Phi": // GREEK CAPITAL LETTER PHI + return rune(0x03a6), true + case "Pi": // GREEK CAPITAL LETTER PI + return rune(0x03a0), true + case "PlusMinus": // PLUS-MINUS SIGN + return rune(0xb1), true + case "Poincareplane": // BLACK-LETTER CAPITAL H + return rune(0x210c), true + case "Popf": // DOUBLE-STRUCK CAPITAL P + return rune(0x2119), true + case "Pr": // DOUBLE PRECEDES + return rune(0x2abb), true + case "Precedes": // PRECEDES + return rune(0x227a), true + case "PrecedesEqual": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN + return rune(0x2aaf), true + case "PrecedesSlantEqual": // PRECEDES OR EQUAL TO + return rune(0x227c), true + case "PrecedesTilde": // PRECEDES OR EQUIVALENT TO + return rune(0x227e), true + case "Prime": // DOUBLE PRIME + return rune(0x2033), true + case "Product": // N-ARY PRODUCT + return rune(0x220f), true + case "Proportion": // PROPORTION + return rune(0x2237), true + case "Proportional": // PROPORTIONAL TO + return rune(0x221d), true + case "Pscr": // MATHEMATICAL SCRIPT CAPITAL P + return rune(0x01d4ab), true + case "Psi": // GREEK CAPITAL LETTER PSI + return rune(0x03a8), true } case 'Q': switch name { - case "QUOT": // QUOTATION MARK - return rune(0x22), true - case "Qfr": // MATHEMATICAL FRAKTUR CAPITAL Q - return rune(0x01d514), true - case "Qopf": // DOUBLE-STRUCK CAPITAL Q - return rune(0x211a), true - case "Qscr": // MATHEMATICAL SCRIPT CAPITAL Q - return rune(0x01d4ac), true + case "QUOT": // QUOTATION MARK + return rune(0x22), true + case "Qfr": // MATHEMATICAL FRAKTUR CAPITAL Q + return rune(0x01d514), true + case "Qopf": // DOUBLE-STRUCK CAPITAL Q + return rune(0x211a), true + case "Qscr": // MATHEMATICAL SCRIPT CAPITAL Q + return rune(0x01d4ac), true } case 'R': switch name { - case "RBarr": // RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW - return rune(0x2910), true - case "REG": // REGISTERED SIGN - return rune(0xae), true - case "Racute": // LATIN CAPITAL LETTER R WITH ACUTE - return rune(0x0154), true - case "Rang": // MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET - return rune(0x27eb), true - case "Rarr": // RIGHTWARDS TWO HEADED ARROW - return rune(0x21a0), true - case "Rarrtl": // RIGHTWARDS TWO-HEADED ARROW WITH TAIL - return rune(0x2916), true - case "Rcaron": // LATIN CAPITAL LETTER R WITH CARON - return rune(0x0158), true - case "Rcedil": // LATIN CAPITAL LETTER R WITH CEDILLA - return rune(0x0156), true - case "Rcy": // CYRILLIC CAPITAL LETTER ER - return rune(0x0420), true - case "Re": // BLACK-LETTER CAPITAL R - return rune(0x211c), true - case "ReverseElement": // CONTAINS AS MEMBER - return rune(0x220b), true - case "ReverseEquilibrium": // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON - return rune(0x21cb), true - case "ReverseUpEquilibrium": // DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT - return rune(0x296f), true - case "Rfr": // BLACK-LETTER CAPITAL R - return rune(0x211c), true - case "Rgr": // GREEK CAPITAL LETTER RHO - return rune(0x03a1), true - case "Rho": // GREEK CAPITAL LETTER RHO - return rune(0x03a1), true - case "RightAngleBracket": // MATHEMATICAL RIGHT ANGLE BRACKET - return rune(0x27e9), true - case "RightArrow": // RIGHTWARDS ARROW - return rune(0x2192), true - case "RightArrowBar": // RIGHTWARDS ARROW TO BAR - return rune(0x21e5), true - case "RightArrowLeftArrow": // RIGHTWARDS ARROW OVER LEFTWARDS ARROW - return rune(0x21c4), true - case "RightCeiling": // RIGHT CEILING - return rune(0x2309), true - case "RightDoubleBracket": // MATHEMATICAL RIGHT WHITE SQUARE BRACKET - return rune(0x27e7), true - case "RightDownTeeVector": // DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR - return rune(0x295d), true - case "RightDownVector": // DOWNWARDS HARPOON WITH BARB RIGHTWARDS - return rune(0x21c2), true - case "RightDownVectorBar": // DOWNWARDS HARPOON WITH BARB RIGHT TO BAR - return rune(0x2955), true - case "RightFloor": // RIGHT FLOOR - return rune(0x230b), true - case "RightTee": // RIGHT TACK - return rune(0x22a2), true - case "RightTeeArrow": // RIGHTWARDS ARROW FROM BAR - return rune(0x21a6), true - case "RightTeeVector": // RIGHTWARDS HARPOON WITH BARB UP FROM BAR - return rune(0x295b), true - case "RightTriangle": // CONTAINS AS NORMAL SUBGROUP - return rune(0x22b3), true - case "RightTriangleBar": // VERTICAL BAR BESIDE RIGHT TRIANGLE - return rune(0x29d0), true - case "RightTriangleEqual": // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO - return rune(0x22b5), true - case "RightUpDownVector": // UP BARB RIGHT DOWN BARB RIGHT HARPOON - return rune(0x294f), true - case "RightUpTeeVector": // UPWARDS HARPOON WITH BARB RIGHT FROM BAR - return rune(0x295c), true - case "RightUpVector": // UPWARDS HARPOON WITH BARB RIGHTWARDS - return rune(0x21be), true - case "RightUpVectorBar": // UPWARDS HARPOON WITH BARB RIGHT TO BAR - return rune(0x2954), true - case "RightVector": // RIGHTWARDS HARPOON WITH BARB UPWARDS - return rune(0x21c0), true - case "RightVectorBar": // RIGHTWARDS HARPOON WITH BARB UP TO BAR - return rune(0x2953), true - case "Rightarrow": // RIGHTWARDS DOUBLE ARROW - return rune(0x21d2), true - case "Ropf": // DOUBLE-STRUCK CAPITAL R - return rune(0x211d), true - case "RoundImplies": // RIGHT DOUBLE ARROW WITH ROUNDED HEAD - return rune(0x2970), true - case "Rrightarrow": // RIGHTWARDS TRIPLE ARROW - return rune(0x21db), true - case "Rscr": // SCRIPT CAPITAL R - return rune(0x211b), true - case "Rsh": // UPWARDS ARROW WITH TIP RIGHTWARDS - return rune(0x21b1), true - case "RuleDelayed": // RULE-DELAYED - return rune(0x29f4), true + case "RBarr": // RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW + return rune(0x2910), true + case "REG": // REGISTERED SIGN + return rune(0xae), true + case "Racute": // LATIN CAPITAL LETTER R WITH ACUTE + return rune(0x0154), true + case "Rang": // MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET + return rune(0x27eb), true + case "Rarr": // RIGHTWARDS TWO HEADED ARROW + return rune(0x21a0), true + case "Rarrtl": // RIGHTWARDS TWO-HEADED ARROW WITH TAIL + return rune(0x2916), true + case "Rcaron": // LATIN CAPITAL LETTER R WITH CARON + return rune(0x0158), true + case "Rcedil": // LATIN CAPITAL LETTER R WITH CEDILLA + return rune(0x0156), true + case "Rcy": // CYRILLIC CAPITAL LETTER ER + return rune(0x0420), true + case "Re": // BLACK-LETTER CAPITAL R + return rune(0x211c), true + case "ReverseElement": // CONTAINS AS MEMBER + return rune(0x220b), true + case "ReverseEquilibrium": // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON + return rune(0x21cb), true + case "ReverseUpEquilibrium": // DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT + return rune(0x296f), true + case "Rfr": // BLACK-LETTER CAPITAL R + return rune(0x211c), true + case "Rgr": // GREEK CAPITAL LETTER RHO + return rune(0x03a1), true + case "Rho": // GREEK CAPITAL LETTER RHO + return rune(0x03a1), true + case "RightAngleBracket": // MATHEMATICAL RIGHT ANGLE BRACKET + return rune(0x27e9), true + case "RightArrow": // RIGHTWARDS ARROW + return rune(0x2192), true + case "RightArrowBar": // RIGHTWARDS ARROW TO BAR + return rune(0x21e5), true + case "RightArrowLeftArrow": // RIGHTWARDS ARROW OVER LEFTWARDS ARROW + return rune(0x21c4), true + case "RightCeiling": // RIGHT CEILING + return rune(0x2309), true + case "RightDoubleBracket": // MATHEMATICAL RIGHT WHITE SQUARE BRACKET + return rune(0x27e7), true + case "RightDownTeeVector": // DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR + return rune(0x295d), true + case "RightDownVector": // DOWNWARDS HARPOON WITH BARB RIGHTWARDS + return rune(0x21c2), true + case "RightDownVectorBar": // DOWNWARDS HARPOON WITH BARB RIGHT TO BAR + return rune(0x2955), true + case "RightFloor": // RIGHT FLOOR + return rune(0x230b), true + case "RightTee": // RIGHT TACK + return rune(0x22a2), true + case "RightTeeArrow": // RIGHTWARDS ARROW FROM BAR + return rune(0x21a6), true + case "RightTeeVector": // RIGHTWARDS HARPOON WITH BARB UP FROM BAR + return rune(0x295b), true + case "RightTriangle": // CONTAINS AS NORMAL SUBGROUP + return rune(0x22b3), true + case "RightTriangleBar": // VERTICAL BAR BESIDE RIGHT TRIANGLE + return rune(0x29d0), true + case "RightTriangleEqual": // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO + return rune(0x22b5), true + case "RightUpDownVector": // UP BARB RIGHT DOWN BARB RIGHT HARPOON + return rune(0x294f), true + case "RightUpTeeVector": // UPWARDS HARPOON WITH BARB RIGHT FROM BAR + return rune(0x295c), true + case "RightUpVector": // UPWARDS HARPOON WITH BARB RIGHTWARDS + return rune(0x21be), true + case "RightUpVectorBar": // UPWARDS HARPOON WITH BARB RIGHT TO BAR + return rune(0x2954), true + case "RightVector": // RIGHTWARDS HARPOON WITH BARB UPWARDS + return rune(0x21c0), true + case "RightVectorBar": // RIGHTWARDS HARPOON WITH BARB UP TO BAR + return rune(0x2953), true + case "Rightarrow": // RIGHTWARDS DOUBLE ARROW + return rune(0x21d2), true + case "Ropf": // DOUBLE-STRUCK CAPITAL R + return rune(0x211d), true + case "RoundImplies": // RIGHT DOUBLE ARROW WITH ROUNDED HEAD + return rune(0x2970), true + case "Rrightarrow": // RIGHTWARDS TRIPLE ARROW + return rune(0x21db), true + case "Rscr": // SCRIPT CAPITAL R + return rune(0x211b), true + case "Rsh": // UPWARDS ARROW WITH TIP RIGHTWARDS + return rune(0x21b1), true + case "RuleDelayed": // RULE-DELAYED + return rune(0x29f4), true } case 'S': switch name { - case "SHCHcy": // CYRILLIC CAPITAL LETTER SHCHA - return rune(0x0429), true - case "SHcy": // CYRILLIC CAPITAL LETTER SHA - return rune(0x0428), true - case "SOFTcy": // CYRILLIC CAPITAL LETTER SOFT SIGN - return rune(0x042c), true - case "Sacute": // LATIN CAPITAL LETTER S WITH ACUTE - return rune(0x015a), true - case "Sc": // DOUBLE SUCCEEDS - return rune(0x2abc), true - case "Scaron": // LATIN CAPITAL LETTER S WITH CARON - return rune(0x0160), true - case "Scedil": // LATIN CAPITAL LETTER S WITH CEDILLA - return rune(0x015e), true - case "Scirc": // LATIN CAPITAL LETTER S WITH CIRCUMFLEX - return rune(0x015c), true - case "Scy": // CYRILLIC CAPITAL LETTER ES - return rune(0x0421), true - case "Sfr": // MATHEMATICAL FRAKTUR CAPITAL S - return rune(0x01d516), true - case "Sgr": // GREEK CAPITAL LETTER SIGMA - return rune(0x03a3), true - case "ShortDownArrow": // DOWNWARDS ARROW - return rune(0x2193), true - case "ShortLeftArrow": // LEFTWARDS ARROW - return rune(0x2190), true - case "ShortRightArrow": // RIGHTWARDS ARROW - return rune(0x2192), true - case "ShortUpArrow": // UPWARDS ARROW - return rune(0x2191), true - case "Sigma": // GREEK CAPITAL LETTER SIGMA - return rune(0x03a3), true - case "SmallCircle": // RING OPERATOR - return rune(0x2218), true - case "Sopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL S - return rune(0x01d54a), true - case "Sqrt": // SQUARE ROOT - return rune(0x221a), true - case "Square": // WHITE SQUARE - return rune(0x25a1), true - case "SquareIntersection": // SQUARE CAP - return rune(0x2293), true - case "SquareSubset": // SQUARE IMAGE OF - return rune(0x228f), true - case "SquareSubsetEqual": // SQUARE IMAGE OF OR EQUAL TO - return rune(0x2291), true - case "SquareSuperset": // SQUARE ORIGINAL OF - return rune(0x2290), true - case "SquareSupersetEqual": // SQUARE ORIGINAL OF OR EQUAL TO - return rune(0x2292), true - case "SquareUnion": // SQUARE CUP - return rune(0x2294), true - case "Sscr": // MATHEMATICAL SCRIPT CAPITAL S - return rune(0x01d4ae), true - case "Star": // STAR OPERATOR - return rune(0x22c6), true - case "Sub": // DOUBLE SUBSET - return rune(0x22d0), true - case "Subset": // DOUBLE SUBSET - return rune(0x22d0), true - case "SubsetEqual": // SUBSET OF OR EQUAL TO - return rune(0x2286), true - case "Succeeds": // SUCCEEDS - return rune(0x227b), true - case "SucceedsEqual": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2ab0), true - case "SucceedsSlantEqual": // SUCCEEDS OR EQUAL TO - return rune(0x227d), true - case "SucceedsTilde": // SUCCEEDS OR EQUIVALENT TO - return rune(0x227f), true - case "SuchThat": // CONTAINS AS MEMBER - return rune(0x220b), true - case "Sum": // N-ARY SUMMATION - return rune(0x2211), true - case "Sup": // DOUBLE SUPERSET - return rune(0x22d1), true - case "Superset": // SUPERSET OF - return rune(0x2283), true - case "SupersetEqual": // SUPERSET OF OR EQUAL TO - return rune(0x2287), true - case "Supset": // DOUBLE SUPERSET - return rune(0x22d1), true + case "SHCHcy": // CYRILLIC CAPITAL LETTER SHCHA + return rune(0x0429), true + case "SHcy": // CYRILLIC CAPITAL LETTER SHA + return rune(0x0428), true + case "SOFTcy": // CYRILLIC CAPITAL LETTER SOFT SIGN + return rune(0x042c), true + case "Sacute": // LATIN CAPITAL LETTER S WITH ACUTE + return rune(0x015a), true + case "Sc": // DOUBLE SUCCEEDS + return rune(0x2abc), true + case "Scaron": // LATIN CAPITAL LETTER S WITH CARON + return rune(0x0160), true + case "Scedil": // LATIN CAPITAL LETTER S WITH CEDILLA + return rune(0x015e), true + case "Scirc": // LATIN CAPITAL LETTER S WITH CIRCUMFLEX + return rune(0x015c), true + case "Scy": // CYRILLIC CAPITAL LETTER ES + return rune(0x0421), true + case "Sfr": // MATHEMATICAL FRAKTUR CAPITAL S + return rune(0x01d516), true + case "Sgr": // GREEK CAPITAL LETTER SIGMA + return rune(0x03a3), true + case "ShortDownArrow": // DOWNWARDS ARROW + return rune(0x2193), true + case "ShortLeftArrow": // LEFTWARDS ARROW + return rune(0x2190), true + case "ShortRightArrow": // RIGHTWARDS ARROW + return rune(0x2192), true + case "ShortUpArrow": // UPWARDS ARROW + return rune(0x2191), true + case "Sigma": // GREEK CAPITAL LETTER SIGMA + return rune(0x03a3), true + case "SmallCircle": // RING OPERATOR + return rune(0x2218), true + case "Sopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL S + return rune(0x01d54a), true + case "Sqrt": // SQUARE ROOT + return rune(0x221a), true + case "Square": // WHITE SQUARE + return rune(0x25a1), true + case "SquareIntersection": // SQUARE CAP + return rune(0x2293), true + case "SquareSubset": // SQUARE IMAGE OF + return rune(0x228f), true + case "SquareSubsetEqual": // SQUARE IMAGE OF OR EQUAL TO + return rune(0x2291), true + case "SquareSuperset": // SQUARE ORIGINAL OF + return rune(0x2290), true + case "SquareSupersetEqual": // SQUARE ORIGINAL OF OR EQUAL TO + return rune(0x2292), true + case "SquareUnion": // SQUARE CUP + return rune(0x2294), true + case "Sscr": // MATHEMATICAL SCRIPT CAPITAL S + return rune(0x01d4ae), true + case "Star": // STAR OPERATOR + return rune(0x22c6), true + case "Sub": // DOUBLE SUBSET + return rune(0x22d0), true + case "Subset": // DOUBLE SUBSET + return rune(0x22d0), true + case "SubsetEqual": // SUBSET OF OR EQUAL TO + return rune(0x2286), true + case "Succeeds": // SUCCEEDS + return rune(0x227b), true + case "SucceedsEqual": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN + return rune(0x2ab0), true + case "SucceedsSlantEqual": // SUCCEEDS OR EQUAL TO + return rune(0x227d), true + case "SucceedsTilde": // SUCCEEDS OR EQUIVALENT TO + return rune(0x227f), true + case "SuchThat": // CONTAINS AS MEMBER + return rune(0x220b), true + case "Sum": // N-ARY SUMMATION + return rune(0x2211), true + case "Sup": // DOUBLE SUPERSET + return rune(0x22d1), true + case "Superset": // SUPERSET OF + return rune(0x2283), true + case "SupersetEqual": // SUPERSET OF OR EQUAL TO + return rune(0x2287), true + case "Supset": // DOUBLE SUPERSET + return rune(0x22d1), true } case 'T': switch name { - case "THORN": // LATIN CAPITAL LETTER THORN - return rune(0xde), true - case "THgr": // GREEK CAPITAL LETTER THETA - return rune(0x0398), true - case "TRADE": // TRADE MARK SIGN - return rune(0x2122), true - case "TSHcy": // CYRILLIC CAPITAL LETTER TSHE - return rune(0x040b), true - case "TScy": // CYRILLIC CAPITAL LETTER TSE - return rune(0x0426), true - case "Tab": // CHARACTER TABULATION - return rune(0x09), true - case "Tau": // GREEK CAPITAL LETTER TAU - return rune(0x03a4), true - case "Tcaron": // LATIN CAPITAL LETTER T WITH CARON - return rune(0x0164), true - case "Tcedil": // LATIN CAPITAL LETTER T WITH CEDILLA - return rune(0x0162), true - case "Tcy": // CYRILLIC CAPITAL LETTER TE - return rune(0x0422), true - case "Tfr": // MATHEMATICAL FRAKTUR CAPITAL T - return rune(0x01d517), true - case "Tgr": // GREEK CAPITAL LETTER TAU - return rune(0x03a4), true - case "Therefore": // THEREFORE - return rune(0x2234), true - case "Theta": // GREEK CAPITAL LETTER THETA - return rune(0x0398), true - case "Thetav": // GREEK CAPITAL THETA SYMBOL - return rune(0x03f4), true - case "ThickSpace": // space of width 5/18 em - return rune(0x205f), true - case "ThinSpace": // THIN SPACE - return rune(0x2009), true - case "Tilde": // TILDE OPERATOR - return rune(0x223c), true - case "TildeEqual": // ASYMPTOTICALLY EQUAL TO - return rune(0x2243), true - case "TildeFullEqual": // APPROXIMATELY EQUAL TO - return rune(0x2245), true - case "TildeTilde": // ALMOST EQUAL TO - return rune(0x2248), true - case "Topf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL T - return rune(0x01d54b), true - case "TripleDot": // COMBINING THREE DOTS ABOVE - return rune(0x20db), true - case "Tscr": // MATHEMATICAL SCRIPT CAPITAL T - return rune(0x01d4af), true - case "Tstrok": // LATIN CAPITAL LETTER T WITH STROKE - return rune(0x0166), true + case "THORN": // LATIN CAPITAL LETTER THORN + return rune(0xde), true + case "THgr": // GREEK CAPITAL LETTER THETA + return rune(0x0398), true + case "TRADE": // TRADE MARK SIGN + return rune(0x2122), true + case "TSHcy": // CYRILLIC CAPITAL LETTER TSHE + return rune(0x040b), true + case "TScy": // CYRILLIC CAPITAL LETTER TSE + return rune(0x0426), true + case "Tab": // CHARACTER TABULATION + return rune(0x09), true + case "Tau": // GREEK CAPITAL LETTER TAU + return rune(0x03a4), true + case "Tcaron": // LATIN CAPITAL LETTER T WITH CARON + return rune(0x0164), true + case "Tcedil": // LATIN CAPITAL LETTER T WITH CEDILLA + return rune(0x0162), true + case "Tcy": // CYRILLIC CAPITAL LETTER TE + return rune(0x0422), true + case "Tfr": // MATHEMATICAL FRAKTUR CAPITAL T + return rune(0x01d517), true + case "Tgr": // GREEK CAPITAL LETTER TAU + return rune(0x03a4), true + case "Therefore": // THEREFORE + return rune(0x2234), true + case "Theta": // GREEK CAPITAL LETTER THETA + return rune(0x0398), true + case "Thetav": // GREEK CAPITAL THETA SYMBOL + return rune(0x03f4), true + case "ThickSpace": // space of width 5/18 em + return rune(0x205f), true + case "ThinSpace": // THIN SPACE + return rune(0x2009), true + case "Tilde": // TILDE OPERATOR + return rune(0x223c), true + case "TildeEqual": // ASYMPTOTICALLY EQUAL TO + return rune(0x2243), true + case "TildeFullEqual": // APPROXIMATELY EQUAL TO + return rune(0x2245), true + case "TildeTilde": // ALMOST EQUAL TO + return rune(0x2248), true + case "Topf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL T + return rune(0x01d54b), true + case "TripleDot": // COMBINING THREE DOTS ABOVE + return rune(0x20db), true + case "Tscr": // MATHEMATICAL SCRIPT CAPITAL T + return rune(0x01d4af), true + case "Tstrok": // LATIN CAPITAL LETTER T WITH STROKE + return rune(0x0166), true } case 'U': switch name { - case "Uacgr": // GREEK CAPITAL LETTER UPSILON WITH TONOS - return rune(0x038e), true - case "Uacute": // LATIN CAPITAL LETTER U WITH ACUTE - return rune(0xda), true - case "Uarr": // UPWARDS TWO HEADED ARROW - return rune(0x219f), true - case "Uarrocir": // UPWARDS TWO-HEADED ARROW FROM SMALL CIRCLE - return rune(0x2949), true - case "Ubrcy": // CYRILLIC CAPITAL LETTER SHORT U - return rune(0x040e), true - case "Ubreve": // LATIN CAPITAL LETTER U WITH BREVE - return rune(0x016c), true - case "Ucirc": // LATIN CAPITAL LETTER U WITH CIRCUMFLEX - return rune(0xdb), true - case "Ucy": // CYRILLIC CAPITAL LETTER U - return rune(0x0423), true - case "Udblac": // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE - return rune(0x0170), true - case "Udigr": // GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA - return rune(0x03ab), true - case "Ufr": // MATHEMATICAL FRAKTUR CAPITAL U - return rune(0x01d518), true - case "Ugr": // GREEK CAPITAL LETTER UPSILON - return rune(0x03a5), true - case "Ugrave": // LATIN CAPITAL LETTER U WITH GRAVE - return rune(0xd9), true - case "Umacr": // LATIN CAPITAL LETTER U WITH MACRON - return rune(0x016a), true - case "UnderBar": // LOW LINE - return rune(0x5f), true - case "UnderBrace": // BOTTOM CURLY BRACKET - return rune(0x23df), true - case "UnderBracket": // BOTTOM SQUARE BRACKET - return rune(0x23b5), true - case "UnderParenthesis": // BOTTOM PARENTHESIS - return rune(0x23dd), true - case "Union": // N-ARY UNION - return rune(0x22c3), true - case "UnionPlus": // MULTISET UNION - return rune(0x228e), true - case "Uogon": // LATIN CAPITAL LETTER U WITH OGONEK - return rune(0x0172), true - case "Uopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL U - return rune(0x01d54c), true - case "UpArrow": // UPWARDS ARROW - return rune(0x2191), true - case "UpArrowBar": // UPWARDS ARROW TO BAR - return rune(0x2912), true - case "UpArrowDownArrow": // UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW - return rune(0x21c5), true - case "UpDownArrow": // UP DOWN ARROW - return rune(0x2195), true - case "UpEquilibrium": // UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT - return rune(0x296e), true - case "UpTee": // UP TACK - return rune(0x22a5), true - case "UpTeeArrow": // UPWARDS ARROW FROM BAR - return rune(0x21a5), true - case "Uparrow": // UPWARDS DOUBLE ARROW - return rune(0x21d1), true - case "Updownarrow": // UP DOWN DOUBLE ARROW - return rune(0x21d5), true - case "UpperLeftArrow": // NORTH WEST ARROW - return rune(0x2196), true - case "UpperRightArrow": // NORTH EAST ARROW - return rune(0x2197), true - case "Upsi": // GREEK UPSILON WITH HOOK SYMBOL - return rune(0x03d2), true - case "Upsilon": // GREEK CAPITAL LETTER UPSILON - return rune(0x03a5), true - case "Uring": // LATIN CAPITAL LETTER U WITH RING ABOVE - return rune(0x016e), true - case "Uscr": // MATHEMATICAL SCRIPT CAPITAL U - return rune(0x01d4b0), true - case "Utilde": // LATIN CAPITAL LETTER U WITH TILDE - return rune(0x0168), true - case "Uuml": // LATIN CAPITAL LETTER U WITH DIAERESIS - return rune(0xdc), true + case "Uacgr": // GREEK CAPITAL LETTER UPSILON WITH TONOS + return rune(0x038e), true + case "Uacute": // LATIN CAPITAL LETTER U WITH ACUTE + return rune(0xda), true + case "Uarr": // UPWARDS TWO HEADED ARROW + return rune(0x219f), true + case "Uarrocir": // UPWARDS TWO-HEADED ARROW FROM SMALL CIRCLE + return rune(0x2949), true + case "Ubrcy": // CYRILLIC CAPITAL LETTER SHORT U + return rune(0x040e), true + case "Ubreve": // LATIN CAPITAL LETTER U WITH BREVE + return rune(0x016c), true + case "Ucirc": // LATIN CAPITAL LETTER U WITH CIRCUMFLEX + return rune(0xdb), true + case "Ucy": // CYRILLIC CAPITAL LETTER U + return rune(0x0423), true + case "Udblac": // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE + return rune(0x0170), true + case "Udigr": // GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA + return rune(0x03ab), true + case "Ufr": // MATHEMATICAL FRAKTUR CAPITAL U + return rune(0x01d518), true + case "Ugr": // GREEK CAPITAL LETTER UPSILON + return rune(0x03a5), true + case "Ugrave": // LATIN CAPITAL LETTER U WITH GRAVE + return rune(0xd9), true + case "Umacr": // LATIN CAPITAL LETTER U WITH MACRON + return rune(0x016a), true + case "UnderBar": // LOW LINE + return rune(0x5f), true + case "UnderBrace": // BOTTOM CURLY BRACKET + return rune(0x23df), true + case "UnderBracket": // BOTTOM SQUARE BRACKET + return rune(0x23b5), true + case "UnderParenthesis": // BOTTOM PARENTHESIS + return rune(0x23dd), true + case "Union": // N-ARY UNION + return rune(0x22c3), true + case "UnionPlus": // MULTISET UNION + return rune(0x228e), true + case "Uogon": // LATIN CAPITAL LETTER U WITH OGONEK + return rune(0x0172), true + case "Uopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL U + return rune(0x01d54c), true + case "UpArrow": // UPWARDS ARROW + return rune(0x2191), true + case "UpArrowBar": // UPWARDS ARROW TO BAR + return rune(0x2912), true + case "UpArrowDownArrow": // UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW + return rune(0x21c5), true + case "UpDownArrow": // UP DOWN ARROW + return rune(0x2195), true + case "UpEquilibrium": // UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT + return rune(0x296e), true + case "UpTee": // UP TACK + return rune(0x22a5), true + case "UpTeeArrow": // UPWARDS ARROW FROM BAR + return rune(0x21a5), true + case "Uparrow": // UPWARDS DOUBLE ARROW + return rune(0x21d1), true + case "Updownarrow": // UP DOWN DOUBLE ARROW + return rune(0x21d5), true + case "UpperLeftArrow": // NORTH WEST ARROW + return rune(0x2196), true + case "UpperRightArrow": // NORTH EAST ARROW + return rune(0x2197), true + case "Upsi": // GREEK UPSILON WITH HOOK SYMBOL + return rune(0x03d2), true + case "Upsilon": // GREEK CAPITAL LETTER UPSILON + return rune(0x03a5), true + case "Uring": // LATIN CAPITAL LETTER U WITH RING ABOVE + return rune(0x016e), true + case "Uscr": // MATHEMATICAL SCRIPT CAPITAL U + return rune(0x01d4b0), true + case "Utilde": // LATIN CAPITAL LETTER U WITH TILDE + return rune(0x0168), true + case "Uuml": // LATIN CAPITAL LETTER U WITH DIAERESIS + return rune(0xdc), true } case 'V': switch name { - case "VDash": // DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE - return rune(0x22ab), true - case "Vbar": // DOUBLE UP TACK - return rune(0x2aeb), true - case "Vcy": // CYRILLIC CAPITAL LETTER VE - return rune(0x0412), true - case "Vdash": // FORCES - return rune(0x22a9), true - case "Vdashl": // LONG DASH FROM LEFT MEMBER OF DOUBLE VERTICAL - return rune(0x2ae6), true - case "Vee": // N-ARY LOGICAL OR - return rune(0x22c1), true - case "Verbar": // DOUBLE VERTICAL LINE - return rune(0x2016), true - case "Vert": // DOUBLE VERTICAL LINE - return rune(0x2016), true - case "VerticalBar": // DIVIDES - return rune(0x2223), true - case "VerticalLine": // VERTICAL LINE - return rune(0x7c), true - case "VerticalSeparator": // LIGHT VERTICAL BAR - return rune(0x2758), true - case "VerticalTilde": // WREATH PRODUCT - return rune(0x2240), true - case "VeryThinSpace": // HAIR SPACE - return rune(0x200a), true - case "Vfr": // MATHEMATICAL FRAKTUR CAPITAL V - return rune(0x01d519), true - case "Vopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL V - return rune(0x01d54d), true - case "Vscr": // MATHEMATICAL SCRIPT CAPITAL V - return rune(0x01d4b1), true - case "Vvdash": // TRIPLE VERTICAL BAR RIGHT TURNSTILE - return rune(0x22aa), true + case "VDash": // DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE + return rune(0x22ab), true + case "Vbar": // DOUBLE UP TACK + return rune(0x2aeb), true + case "Vcy": // CYRILLIC CAPITAL LETTER VE + return rune(0x0412), true + case "Vdash": // FORCES + return rune(0x22a9), true + case "Vdashl": // LONG DASH FROM LEFT MEMBER OF DOUBLE VERTICAL + return rune(0x2ae6), true + case "Vee": // N-ARY LOGICAL OR + return rune(0x22c1), true + case "Verbar": // DOUBLE VERTICAL LINE + return rune(0x2016), true + case "Vert": // DOUBLE VERTICAL LINE + return rune(0x2016), true + case "VerticalBar": // DIVIDES + return rune(0x2223), true + case "VerticalLine": // VERTICAL LINE + return rune(0x7c), true + case "VerticalSeparator": // LIGHT VERTICAL BAR + return rune(0x2758), true + case "VerticalTilde": // WREATH PRODUCT + return rune(0x2240), true + case "VeryThinSpace": // HAIR SPACE + return rune(0x200a), true + case "Vfr": // MATHEMATICAL FRAKTUR CAPITAL V + return rune(0x01d519), true + case "Vopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL V + return rune(0x01d54d), true + case "Vscr": // MATHEMATICAL SCRIPT CAPITAL V + return rune(0x01d4b1), true + case "Vvdash": // TRIPLE VERTICAL BAR RIGHT TURNSTILE + return rune(0x22aa), true } case 'W': switch name { - case "Wcirc": // LATIN CAPITAL LETTER W WITH CIRCUMFLEX - return rune(0x0174), true - case "Wedge": // N-ARY LOGICAL AND - return rune(0x22c0), true - case "Wfr": // MATHEMATICAL FRAKTUR CAPITAL W - return rune(0x01d51a), true - case "Wopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL W - return rune(0x01d54e), true - case "Wscr": // MATHEMATICAL SCRIPT CAPITAL W - return rune(0x01d4b2), true + case "Wcirc": // LATIN CAPITAL LETTER W WITH CIRCUMFLEX + return rune(0x0174), true + case "Wedge": // N-ARY LOGICAL AND + return rune(0x22c0), true + case "Wfr": // MATHEMATICAL FRAKTUR CAPITAL W + return rune(0x01d51a), true + case "Wopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL W + return rune(0x01d54e), true + case "Wscr": // MATHEMATICAL SCRIPT CAPITAL W + return rune(0x01d4b2), true } case 'X': switch name { - case "Xfr": // MATHEMATICAL FRAKTUR CAPITAL X - return rune(0x01d51b), true - case "Xgr": // GREEK CAPITAL LETTER XI - return rune(0x039e), true - case "Xi": // GREEK CAPITAL LETTER XI - return rune(0x039e), true - case "Xopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL X - return rune(0x01d54f), true - case "Xscr": // MATHEMATICAL SCRIPT CAPITAL X - return rune(0x01d4b3), true + case "Xfr": // MATHEMATICAL FRAKTUR CAPITAL X + return rune(0x01d51b), true + case "Xgr": // GREEK CAPITAL LETTER XI + return rune(0x039e), true + case "Xi": // GREEK CAPITAL LETTER XI + return rune(0x039e), true + case "Xopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL X + return rune(0x01d54f), true + case "Xscr": // MATHEMATICAL SCRIPT CAPITAL X + return rune(0x01d4b3), true } case 'Y': switch name { - case "YAcy": // CYRILLIC CAPITAL LETTER YA - return rune(0x042f), true - case "YIcy": // CYRILLIC CAPITAL LETTER YI - return rune(0x0407), true - case "YUcy": // CYRILLIC CAPITAL LETTER YU - return rune(0x042e), true - case "Yacute": // LATIN CAPITAL LETTER Y WITH ACUTE - return rune(0xdd), true - case "Ycirc": // LATIN CAPITAL LETTER Y WITH CIRCUMFLEX - return rune(0x0176), true - case "Ycy": // CYRILLIC CAPITAL LETTER YERU - return rune(0x042b), true - case "Yfr": // MATHEMATICAL FRAKTUR CAPITAL Y - return rune(0x01d51c), true - case "Yopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL Y - return rune(0x01d550), true - case "Yscr": // MATHEMATICAL SCRIPT CAPITAL Y - return rune(0x01d4b4), true - case "Yuml": // LATIN CAPITAL LETTER Y WITH DIAERESIS - return rune(0x0178), true + case "YAcy": // CYRILLIC CAPITAL LETTER YA + return rune(0x042f), true + case "YIcy": // CYRILLIC CAPITAL LETTER YI + return rune(0x0407), true + case "YUcy": // CYRILLIC CAPITAL LETTER YU + return rune(0x042e), true + case "Yacute": // LATIN CAPITAL LETTER Y WITH ACUTE + return rune(0xdd), true + case "Ycirc": // LATIN CAPITAL LETTER Y WITH CIRCUMFLEX + return rune(0x0176), true + case "Ycy": // CYRILLIC CAPITAL LETTER YERU + return rune(0x042b), true + case "Yfr": // MATHEMATICAL FRAKTUR CAPITAL Y + return rune(0x01d51c), true + case "Yopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL Y + return rune(0x01d550), true + case "Yscr": // MATHEMATICAL SCRIPT CAPITAL Y + return rune(0x01d4b4), true + case "Yuml": // LATIN CAPITAL LETTER Y WITH DIAERESIS + return rune(0x0178), true } case 'Z': switch name { - case "ZHcy": // CYRILLIC CAPITAL LETTER ZHE - return rune(0x0416), true - case "Zacute": // LATIN CAPITAL LETTER Z WITH ACUTE - return rune(0x0179), true - case "Zcaron": // LATIN CAPITAL LETTER Z WITH CARON - return rune(0x017d), true - case "Zcy": // CYRILLIC CAPITAL LETTER ZE - return rune(0x0417), true - case "Zdot": // LATIN CAPITAL LETTER Z WITH DOT ABOVE - return rune(0x017b), true - case "ZeroWidthSpace": // ZERO WIDTH SPACE - return rune(0x200b), true - case "Zeta": // GREEK CAPITAL LETTER ZETA - return rune(0x0396), true - case "Zfr": // BLACK-LETTER CAPITAL Z - return rune(0x2128), true - case "Zgr": // GREEK CAPITAL LETTER ZETA - return rune(0x0396), true - case "Zopf": // DOUBLE-STRUCK CAPITAL Z - return rune(0x2124), true - case "Zscr": // MATHEMATICAL SCRIPT CAPITAL Z - return rune(0x01d4b5), true + case "ZHcy": // CYRILLIC CAPITAL LETTER ZHE + return rune(0x0416), true + case "Zacute": // LATIN CAPITAL LETTER Z WITH ACUTE + return rune(0x0179), true + case "Zcaron": // LATIN CAPITAL LETTER Z WITH CARON + return rune(0x017d), true + case "Zcy": // CYRILLIC CAPITAL LETTER ZE + return rune(0x0417), true + case "Zdot": // LATIN CAPITAL LETTER Z WITH DOT ABOVE + return rune(0x017b), true + case "ZeroWidthSpace": // ZERO WIDTH SPACE + return rune(0x200b), true + case "Zeta": // GREEK CAPITAL LETTER ZETA + return rune(0x0396), true + case "Zfr": // BLACK-LETTER CAPITAL Z + return rune(0x2128), true + case "Zgr": // GREEK CAPITAL LETTER ZETA + return rune(0x0396), true + case "Zopf": // DOUBLE-STRUCK CAPITAL Z + return rune(0x2124), true + case "Zscr": // MATHEMATICAL SCRIPT CAPITAL Z + return rune(0x01d4b5), true } case 'a': switch name { - case "aacgr": // GREEK SMALL LETTER ALPHA WITH TONOS - return rune(0x03ac), true - case "aacute": // LATIN SMALL LETTER A WITH ACUTE - return rune(0xe1), true - case "abreve": // LATIN SMALL LETTER A WITH BREVE - return rune(0x0103), true - case "ac": // INVERTED LAZY S - return rune(0x223e), true - case "acE": // INVERTED LAZY S with double underline - return rune(0x223e), true - case "acd": // SINE WAVE - return rune(0x223f), true - case "acirc": // LATIN SMALL LETTER A WITH CIRCUMFLEX - return rune(0xe2), true - case "actuary": // COMBINING ANNUITY SYMBOL - return rune(0x20e7), true - case "acute": // ACUTE ACCENT - return rune(0xb4), true - case "acy": // CYRILLIC SMALL LETTER A - return rune(0x0430), true - case "aelig": // LATIN SMALL LETTER AE - return rune(0xe6), true - case "af": // FUNCTION APPLICATION - return rune(0x2061), true - case "afr": // MATHEMATICAL FRAKTUR SMALL A - return rune(0x01d51e), true - case "agr": // GREEK SMALL LETTER ALPHA - return rune(0x03b1), true - case "agrave": // LATIN SMALL LETTER A WITH GRAVE - return rune(0xe0), true - case "alefsym": // ALEF SYMBOL - return rune(0x2135), true - case "aleph": // ALEF SYMBOL - return rune(0x2135), true - case "alpha": // GREEK SMALL LETTER ALPHA - return rune(0x03b1), true - case "amacr": // LATIN SMALL LETTER A WITH MACRON - return rune(0x0101), true - case "amalg": // AMALGAMATION OR COPRODUCT - return rune(0x2a3f), true - case "amp": // AMPERSAND - return rune(0x26), true - case "and": // LOGICAL AND - return rune(0x2227), true - case "andand": // TWO INTERSECTING LOGICAL AND - return rune(0x2a55), true - case "andd": // LOGICAL AND WITH HORIZONTAL DASH - return rune(0x2a5c), true - case "andslope": // SLOPING LARGE AND - return rune(0x2a58), true - case "andv": // LOGICAL AND WITH MIDDLE STEM - return rune(0x2a5a), true - case "ang": // ANGLE - return rune(0x2220), true - case "ang90": // RIGHT ANGLE - return rune(0x221f), true - case "angdnl": // TURNED ANGLE - return rune(0x29a2), true - case "angdnr": // ACUTE ANGLE - return rune(0x299f), true - case "ange": // ANGLE WITH UNDERBAR - return rune(0x29a4), true - case "angle": // ANGLE - return rune(0x2220), true - case "angles": // ANGLE WITH S INSIDE - return rune(0x299e), true - case "angmsd": // MEASURED ANGLE - return rune(0x2221), true - case "angmsdaa": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND RIGHT - return rune(0x29a8), true - case "angmsdab": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND LEFT - return rune(0x29a9), true - case "angmsdac": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND RIGHT - return rune(0x29aa), true - case "angmsdad": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND LEFT - return rune(0x29ab), true - case "angmsdae": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND UP - return rune(0x29ac), true - case "angmsdaf": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND UP - return rune(0x29ad), true - case "angmsdag": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND DOWN - return rune(0x29ae), true - case "angmsdah": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND DOWN - return rune(0x29af), true - case "angrt": // RIGHT ANGLE - return rune(0x221f), true - case "angrtvb": // RIGHT ANGLE WITH ARC - return rune(0x22be), true - case "angrtvbd": // MEASURED RIGHT ANGLE WITH DOT - return rune(0x299d), true - case "angsph": // SPHERICAL ANGLE - return rune(0x2222), true - case "angst": // LATIN CAPITAL LETTER A WITH RING ABOVE - return rune(0xc5), true - case "angupl": // REVERSED ANGLE - return rune(0x29a3), true - case "angzarr": // RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW - return rune(0x237c), true - case "aogon": // LATIN SMALL LETTER A WITH OGONEK - return rune(0x0105), true - case "aopf": // MATHEMATICAL DOUBLE-STRUCK SMALL A - return rune(0x01d552), true - case "ap": // ALMOST EQUAL TO - return rune(0x2248), true - case "apE": // APPROXIMATELY EQUAL OR EQUAL TO - return rune(0x2a70), true - case "apacir": // ALMOST EQUAL TO WITH CIRCUMFLEX ACCENT - return rune(0x2a6f), true - case "ape": // ALMOST EQUAL OR EQUAL TO - return rune(0x224a), true - case "apid": // TRIPLE TILDE - return rune(0x224b), true - case "apos": // APOSTROPHE - return rune(0x27), true - case "approx": // ALMOST EQUAL TO - return rune(0x2248), true - case "approxeq": // ALMOST EQUAL OR EQUAL TO - return rune(0x224a), true - case "aring": // LATIN SMALL LETTER A WITH RING ABOVE - return rune(0xe5), true - case "arrllsr": // LEFTWARDS ARROW ABOVE SHORT RIGHTWARDS ARROW - return rune(0x2943), true - case "arrlrsl": // RIGHTWARDS ARROW ABOVE SHORT LEFTWARDS ARROW - return rune(0x2942), true - case "arrsrll": // SHORT RIGHTWARDS ARROW ABOVE LEFTWARDS ARROW - return rune(0x2944), true - case "ascr": // MATHEMATICAL SCRIPT SMALL A - return rune(0x01d4b6), true - case "ast": // ASTERISK - return rune(0x2a), true - case "astb": // SQUARED ASTERISK - return rune(0x29c6), true - case "asymp": // ALMOST EQUAL TO - return rune(0x2248), true - case "asympeq": // EQUIVALENT TO - return rune(0x224d), true - case "atilde": // LATIN SMALL LETTER A WITH TILDE - return rune(0xe3), true - case "auml": // LATIN SMALL LETTER A WITH DIAERESIS - return rune(0xe4), true - case "awconint": // ANTICLOCKWISE CONTOUR INTEGRAL - return rune(0x2233), true - case "awint": // ANTICLOCKWISE INTEGRATION - return rune(0x2a11), true + case "aacgr": // GREEK SMALL LETTER ALPHA WITH TONOS + return rune(0x03ac), true + case "aacute": // LATIN SMALL LETTER A WITH ACUTE + return rune(0xe1), true + case "abreve": // LATIN SMALL LETTER A WITH BREVE + return rune(0x0103), true + case "ac": // INVERTED LAZY S + return rune(0x223e), true + case "acE": // INVERTED LAZY S with double underline + return rune(0x223e), true + case "acd": // SINE WAVE + return rune(0x223f), true + case "acirc": // LATIN SMALL LETTER A WITH CIRCUMFLEX + return rune(0xe2), true + case "actuary": // COMBINING ANNUITY SYMBOL + return rune(0x20e7), true + case "acute": // ACUTE ACCENT + return rune(0xb4), true + case "acy": // CYRILLIC SMALL LETTER A + return rune(0x0430), true + case "aelig": // LATIN SMALL LETTER AE + return rune(0xe6), true + case "af": // FUNCTION APPLICATION + return rune(0x2061), true + case "afr": // MATHEMATICAL FRAKTUR SMALL A + return rune(0x01d51e), true + case "agr": // GREEK SMALL LETTER ALPHA + return rune(0x03b1), true + case "agrave": // LATIN SMALL LETTER A WITH GRAVE + return rune(0xe0), true + case "alefsym": // ALEF SYMBOL + return rune(0x2135), true + case "aleph": // ALEF SYMBOL + return rune(0x2135), true + case "alpha": // GREEK SMALL LETTER ALPHA + return rune(0x03b1), true + case "amacr": // LATIN SMALL LETTER A WITH MACRON + return rune(0x0101), true + case "amalg": // AMALGAMATION OR COPRODUCT + return rune(0x2a3f), true + case "amp": // AMPERSAND + return rune(0x26), true + case "and": // LOGICAL AND + return rune(0x2227), true + case "andand": // TWO INTERSECTING LOGICAL AND + return rune(0x2a55), true + case "andd": // LOGICAL AND WITH HORIZONTAL DASH + return rune(0x2a5c), true + case "andslope": // SLOPING LARGE AND + return rune(0x2a58), true + case "andv": // LOGICAL AND WITH MIDDLE STEM + return rune(0x2a5a), true + case "ang": // ANGLE + return rune(0x2220), true + case "ang90": // RIGHT ANGLE + return rune(0x221f), true + case "angdnl": // TURNED ANGLE + return rune(0x29a2), true + case "angdnr": // ACUTE ANGLE + return rune(0x299f), true + case "ange": // ANGLE WITH UNDERBAR + return rune(0x29a4), true + case "angle": // ANGLE + return rune(0x2220), true + case "angles": // ANGLE WITH S INSIDE + return rune(0x299e), true + case "angmsd": // MEASURED ANGLE + return rune(0x2221), true + case "angmsdaa": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND RIGHT + return rune(0x29a8), true + case "angmsdab": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND LEFT + return rune(0x29a9), true + case "angmsdac": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND RIGHT + return rune(0x29aa), true + case "angmsdad": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND LEFT + return rune(0x29ab), true + case "angmsdae": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND UP + return rune(0x29ac), true + case "angmsdaf": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND UP + return rune(0x29ad), true + case "angmsdag": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND DOWN + return rune(0x29ae), true + case "angmsdah": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND DOWN + return rune(0x29af), true + case "angrt": // RIGHT ANGLE + return rune(0x221f), true + case "angrtvb": // RIGHT ANGLE WITH ARC + return rune(0x22be), true + case "angrtvbd": // MEASURED RIGHT ANGLE WITH DOT + return rune(0x299d), true + case "angsph": // SPHERICAL ANGLE + return rune(0x2222), true + case "angst": // LATIN CAPITAL LETTER A WITH RING ABOVE + return rune(0xc5), true + case "angupl": // REVERSED ANGLE + return rune(0x29a3), true + case "angzarr": // RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW + return rune(0x237c), true + case "aogon": // LATIN SMALL LETTER A WITH OGONEK + return rune(0x0105), true + case "aopf": // MATHEMATICAL DOUBLE-STRUCK SMALL A + return rune(0x01d552), true + case "ap": // ALMOST EQUAL TO + return rune(0x2248), true + case "apE": // APPROXIMATELY EQUAL OR EQUAL TO + return rune(0x2a70), true + case "apacir": // ALMOST EQUAL TO WITH CIRCUMFLEX ACCENT + return rune(0x2a6f), true + case "ape": // ALMOST EQUAL OR EQUAL TO + return rune(0x224a), true + case "apid": // TRIPLE TILDE + return rune(0x224b), true + case "apos": // APOSTROPHE + return rune(0x27), true + case "approx": // ALMOST EQUAL TO + return rune(0x2248), true + case "approxeq": // ALMOST EQUAL OR EQUAL TO + return rune(0x224a), true + case "aring": // LATIN SMALL LETTER A WITH RING ABOVE + return rune(0xe5), true + case "arrllsr": // LEFTWARDS ARROW ABOVE SHORT RIGHTWARDS ARROW + return rune(0x2943), true + case "arrlrsl": // RIGHTWARDS ARROW ABOVE SHORT LEFTWARDS ARROW + return rune(0x2942), true + case "arrsrll": // SHORT RIGHTWARDS ARROW ABOVE LEFTWARDS ARROW + return rune(0x2944), true + case "ascr": // MATHEMATICAL SCRIPT SMALL A + return rune(0x01d4b6), true + case "ast": // ASTERISK + return rune(0x2a), true + case "astb": // SQUARED ASTERISK + return rune(0x29c6), true + case "asymp": // ALMOST EQUAL TO + return rune(0x2248), true + case "asympeq": // EQUIVALENT TO + return rune(0x224d), true + case "atilde": // LATIN SMALL LETTER A WITH TILDE + return rune(0xe3), true + case "auml": // LATIN SMALL LETTER A WITH DIAERESIS + return rune(0xe4), true + case "awconint": // ANTICLOCKWISE CONTOUR INTEGRAL + return rune(0x2233), true + case "awint": // ANTICLOCKWISE INTEGRATION + return rune(0x2a11), true } case 'b': switch name { - case "b.Delta": // MATHEMATICAL BOLD CAPITAL DELTA - return rune(0x01d6ab), true - case "b.Gamma": // MATHEMATICAL BOLD CAPITAL GAMMA - return rune(0x01d6aa), true - case "b.Gammad": // MATHEMATICAL BOLD CAPITAL DIGAMMA - return rune(0x01d7ca), true - case "b.Lambda": // MATHEMATICAL BOLD CAPITAL LAMDA - return rune(0x01d6b2), true - case "b.Omega": // MATHEMATICAL BOLD CAPITAL OMEGA - return rune(0x01d6c0), true - case "b.Phi": // MATHEMATICAL BOLD CAPITAL PHI - return rune(0x01d6bd), true - case "b.Pi": // MATHEMATICAL BOLD CAPITAL PI - return rune(0x01d6b7), true - case "b.Psi": // MATHEMATICAL BOLD CAPITAL PSI - return rune(0x01d6bf), true - case "b.Sigma": // MATHEMATICAL BOLD CAPITAL SIGMA - return rune(0x01d6ba), true - case "b.Theta": // MATHEMATICAL BOLD CAPITAL THETA - return rune(0x01d6af), true - case "b.Upsi": // MATHEMATICAL BOLD CAPITAL UPSILON - return rune(0x01d6bc), true - case "b.Xi": // MATHEMATICAL BOLD CAPITAL XI - return rune(0x01d6b5), true - case "b.alpha": // MATHEMATICAL BOLD SMALL ALPHA - return rune(0x01d6c2), true - case "b.beta": // MATHEMATICAL BOLD SMALL BETA - return rune(0x01d6c3), true - case "b.chi": // MATHEMATICAL BOLD SMALL CHI - return rune(0x01d6d8), true - case "b.delta": // MATHEMATICAL BOLD SMALL DELTA - return rune(0x01d6c5), true - case "b.epsi": // MATHEMATICAL BOLD SMALL EPSILON - return rune(0x01d6c6), true - case "b.epsiv": // MATHEMATICAL BOLD EPSILON SYMBOL - return rune(0x01d6dc), true - case "b.eta": // MATHEMATICAL BOLD SMALL ETA - return rune(0x01d6c8), true - case "b.gamma": // MATHEMATICAL BOLD SMALL GAMMA - return rune(0x01d6c4), true - case "b.gammad": // MATHEMATICAL BOLD SMALL DIGAMMA - return rune(0x01d7cb), true - case "b.iota": // MATHEMATICAL BOLD SMALL IOTA - return rune(0x01d6ca), true - case "b.kappa": // MATHEMATICAL BOLD SMALL KAPPA - return rune(0x01d6cb), true - case "b.kappav": // MATHEMATICAL BOLD KAPPA SYMBOL - return rune(0x01d6de), true - case "b.lambda": // MATHEMATICAL BOLD SMALL LAMDA - return rune(0x01d6cc), true - case "b.mu": // MATHEMATICAL BOLD SMALL MU - return rune(0x01d6cd), true - case "b.nu": // MATHEMATICAL BOLD SMALL NU - return rune(0x01d6ce), true - case "b.omega": // MATHEMATICAL BOLD SMALL OMEGA - return rune(0x01d6da), true - case "b.phi": // MATHEMATICAL BOLD SMALL PHI - return rune(0x01d6d7), true - case "b.phiv": // MATHEMATICAL BOLD PHI SYMBOL - return rune(0x01d6df), true - case "b.pi": // MATHEMATICAL BOLD SMALL PI - return rune(0x01d6d1), true - case "b.piv": // MATHEMATICAL BOLD PI SYMBOL - return rune(0x01d6e1), true - case "b.psi": // MATHEMATICAL BOLD SMALL PSI - return rune(0x01d6d9), true - case "b.rho": // MATHEMATICAL BOLD SMALL RHO - return rune(0x01d6d2), true - case "b.rhov": // MATHEMATICAL BOLD RHO SYMBOL - return rune(0x01d6e0), true - case "b.sigma": // MATHEMATICAL BOLD SMALL SIGMA - return rune(0x01d6d4), true - case "b.sigmav": // MATHEMATICAL BOLD SMALL FINAL SIGMA - return rune(0x01d6d3), true - case "b.tau": // MATHEMATICAL BOLD SMALL TAU - return rune(0x01d6d5), true - case "b.thetas": // MATHEMATICAL BOLD SMALL THETA - return rune(0x01d6c9), true - case "b.thetav": // MATHEMATICAL BOLD THETA SYMBOL - return rune(0x01d6dd), true - case "b.upsi": // MATHEMATICAL BOLD SMALL UPSILON - return rune(0x01d6d6), true - case "b.xi": // MATHEMATICAL BOLD SMALL XI - return rune(0x01d6cf), true - case "b.zeta": // MATHEMATICAL BOLD SMALL ZETA - return rune(0x01d6c7), true - case "bNot": // REVERSED DOUBLE STROKE NOT SIGN - return rune(0x2aed), true - case "backcong": // ALL EQUAL TO - return rune(0x224c), true - case "backepsilon": // GREEK REVERSED LUNATE EPSILON SYMBOL - return rune(0x03f6), true - case "backprime": // REVERSED PRIME - return rune(0x2035), true - case "backsim": // REVERSED TILDE - return rune(0x223d), true - case "backsimeq": // REVERSED TILDE EQUALS - return rune(0x22cd), true - case "barV": // DOUBLE DOWN TACK - return rune(0x2aea), true - case "barvee": // NOR - return rune(0x22bd), true - case "barwed": // PROJECTIVE - return rune(0x2305), true - case "barwedge": // PROJECTIVE - return rune(0x2305), true - case "bbrk": // BOTTOM SQUARE BRACKET - return rune(0x23b5), true - case "bbrktbrk": // BOTTOM SQUARE BRACKET OVER TOP SQUARE BRACKET - return rune(0x23b6), true - case "bcong": // ALL EQUAL TO - return rune(0x224c), true - case "bcy": // CYRILLIC SMALL LETTER BE - return rune(0x0431), true - case "bdlhar": // DOWNWARDS HARPOON WITH BARB LEFT FROM BAR - return rune(0x2961), true - case "bdquo": // DOUBLE LOW-9 QUOTATION MARK - return rune(0x201e), true - case "bdrhar": // DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR - return rune(0x295d), true - case "becaus": // BECAUSE - return rune(0x2235), true - case "because": // BECAUSE - return rune(0x2235), true - case "bemptyv": // REVERSED EMPTY SET - return rune(0x29b0), true - case "bepsi": // GREEK REVERSED LUNATE EPSILON SYMBOL - return rune(0x03f6), true - case "bernou": // SCRIPT CAPITAL B - return rune(0x212c), true - case "beta": // GREEK SMALL LETTER BETA - return rune(0x03b2), true - case "beth": // BET SYMBOL - return rune(0x2136), true - case "between": // BETWEEN - return rune(0x226c), true - case "bfr": // MATHEMATICAL FRAKTUR SMALL B - return rune(0x01d51f), true - case "bgr": // GREEK SMALL LETTER BETA - return rune(0x03b2), true - case "bigcap": // N-ARY INTERSECTION - return rune(0x22c2), true - case "bigcirc": // LARGE CIRCLE - return rune(0x25ef), true - case "bigcup": // N-ARY UNION - return rune(0x22c3), true - case "bigodot": // N-ARY CIRCLED DOT OPERATOR - return rune(0x2a00), true - case "bigoplus": // N-ARY CIRCLED PLUS OPERATOR - return rune(0x2a01), true - case "bigotimes": // N-ARY CIRCLED TIMES OPERATOR - return rune(0x2a02), true - case "bigsqcup": // N-ARY SQUARE UNION OPERATOR - return rune(0x2a06), true - case "bigstar": // BLACK STAR - return rune(0x2605), true - case "bigtriangledown": // WHITE DOWN-POINTING TRIANGLE - return rune(0x25bd), true - case "bigtriangleup": // WHITE UP-POINTING TRIANGLE - return rune(0x25b3), true - case "biguplus": // N-ARY UNION OPERATOR WITH PLUS - return rune(0x2a04), true - case "bigvee": // N-ARY LOGICAL OR - return rune(0x22c1), true - case "bigwedge": // N-ARY LOGICAL AND - return rune(0x22c0), true - case "bkarow": // RIGHTWARDS DOUBLE DASH ARROW - return rune(0x290d), true - case "blacklozenge": // BLACK LOZENGE - return rune(0x29eb), true - case "blacksquare": // BLACK SMALL SQUARE - return rune(0x25aa), true - case "blacktriangle": // BLACK UP-POINTING SMALL TRIANGLE - return rune(0x25b4), true - case "blacktriangledown": // BLACK DOWN-POINTING SMALL TRIANGLE - return rune(0x25be), true - case "blacktriangleleft": // BLACK LEFT-POINTING SMALL TRIANGLE - return rune(0x25c2), true - case "blacktriangleright": // BLACK RIGHT-POINTING SMALL TRIANGLE - return rune(0x25b8), true - case "blank": // BLANK SYMBOL - return rune(0x2422), true - case "bldhar": // LEFTWARDS HARPOON WITH BARB DOWN FROM BAR - return rune(0x295e), true - case "blk12": // MEDIUM SHADE - return rune(0x2592), true - case "blk14": // LIGHT SHADE - return rune(0x2591), true - case "blk34": // DARK SHADE - return rune(0x2593), true - case "block": // FULL BLOCK - return rune(0x2588), true - case "bluhar": // LEFTWARDS HARPOON WITH BARB UP FROM BAR - return rune(0x295a), true - case "bne": // EQUALS SIGN with reverse slash - return rune(0x3d), true - case "bnequiv": // IDENTICAL TO with reverse slash - return rune(0x2261), true - case "bnot": // REVERSED NOT SIGN - return rune(0x2310), true - case "bopf": // MATHEMATICAL DOUBLE-STRUCK SMALL B - return rune(0x01d553), true - case "bot": // UP TACK - return rune(0x22a5), true - case "bottom": // UP TACK - return rune(0x22a5), true - case "bowtie": // BOWTIE - return rune(0x22c8), true - case "boxDL": // BOX DRAWINGS DOUBLE DOWN AND LEFT - return rune(0x2557), true - case "boxDR": // BOX DRAWINGS DOUBLE DOWN AND RIGHT - return rune(0x2554), true - case "boxDl": // BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE - return rune(0x2556), true - case "boxDr": // BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE - return rune(0x2553), true - case "boxH": // BOX DRAWINGS DOUBLE HORIZONTAL - return rune(0x2550), true - case "boxHD": // BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL - return rune(0x2566), true - case "boxHU": // BOX DRAWINGS DOUBLE UP AND HORIZONTAL - return rune(0x2569), true - case "boxHd": // BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE - return rune(0x2564), true - case "boxHu": // BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE - return rune(0x2567), true - case "boxUL": // BOX DRAWINGS DOUBLE UP AND LEFT - return rune(0x255d), true - case "boxUR": // BOX DRAWINGS DOUBLE UP AND RIGHT - return rune(0x255a), true - case "boxUl": // BOX DRAWINGS UP DOUBLE AND LEFT SINGLE - return rune(0x255c), true - case "boxUr": // BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE - return rune(0x2559), true - case "boxV": // BOX DRAWINGS DOUBLE VERTICAL - return rune(0x2551), true - case "boxVH": // BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL - return rune(0x256c), true - case "boxVL": // BOX DRAWINGS DOUBLE VERTICAL AND LEFT - return rune(0x2563), true - case "boxVR": // BOX DRAWINGS DOUBLE VERTICAL AND RIGHT - return rune(0x2560), true - case "boxVh": // BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE - return rune(0x256b), true - case "boxVl": // BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE - return rune(0x2562), true - case "boxVr": // BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE - return rune(0x255f), true - case "boxbox": // TWO JOINED SQUARES - return rune(0x29c9), true - case "boxdL": // BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE - return rune(0x2555), true - case "boxdR": // BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE - return rune(0x2552), true - case "boxdl": // BOX DRAWINGS LIGHT DOWN AND LEFT - return rune(0x2510), true - case "boxdr": // BOX DRAWINGS LIGHT DOWN AND RIGHT - return rune(0x250c), true - case "boxh": // BOX DRAWINGS LIGHT HORIZONTAL - return rune(0x2500), true - case "boxhD": // BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE - return rune(0x2565), true - case "boxhU": // BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE - return rune(0x2568), true - case "boxhd": // BOX DRAWINGS LIGHT DOWN AND HORIZONTAL - return rune(0x252c), true - case "boxhu": // BOX DRAWINGS LIGHT UP AND HORIZONTAL - return rune(0x2534), true - case "boxminus": // SQUARED MINUS - return rune(0x229f), true - case "boxplus": // SQUARED PLUS - return rune(0x229e), true - case "boxtimes": // SQUARED TIMES - return rune(0x22a0), true - case "boxuL": // BOX DRAWINGS UP SINGLE AND LEFT DOUBLE - return rune(0x255b), true - case "boxuR": // BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE - return rune(0x2558), true - case "boxul": // BOX DRAWINGS LIGHT UP AND LEFT - return rune(0x2518), true - case "boxur": // BOX DRAWINGS LIGHT UP AND RIGHT - return rune(0x2514), true - case "boxv": // BOX DRAWINGS LIGHT VERTICAL - return rune(0x2502), true - case "boxvH": // BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE - return rune(0x256a), true - case "boxvL": // BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE - return rune(0x2561), true - case "boxvR": // BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE - return rune(0x255e), true - case "boxvh": // BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL - return rune(0x253c), true - case "boxvl": // BOX DRAWINGS LIGHT VERTICAL AND LEFT - return rune(0x2524), true - case "boxvr": // BOX DRAWINGS LIGHT VERTICAL AND RIGHT - return rune(0x251c), true - case "bprime": // REVERSED PRIME - return rune(0x2035), true - case "brdhar": // RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR - return rune(0x295f), true - case "breve": // BREVE - return rune(0x02d8), true - case "bruhar": // RIGHTWARDS HARPOON WITH BARB UP FROM BAR - return rune(0x295b), true - case "brvbar": // BROKEN BAR - return rune(0xa6), true - case "bscr": // MATHEMATICAL SCRIPT SMALL B - return rune(0x01d4b7), true - case "bsemi": // REVERSED SEMICOLON - return rune(0x204f), true - case "bsim": // REVERSED TILDE - return rune(0x223d), true - case "bsime": // REVERSED TILDE EQUALS - return rune(0x22cd), true - case "bsol": // REVERSE SOLIDUS - return rune(0x5c), true - case "bsolb": // SQUARED FALLING DIAGONAL SLASH - return rune(0x29c5), true - case "bsolhsub": // REVERSE SOLIDUS PRECEDING SUBSET - return rune(0x27c8), true - case "btimes": // SEMIDIRECT PRODUCT WITH BOTTOM CLOSED - return rune(0x2a32), true - case "bulhar": // UPWARDS HARPOON WITH BARB LEFT FROM BAR - return rune(0x2960), true - case "bull": // BULLET - return rune(0x2022), true - case "bullet": // BULLET - return rune(0x2022), true - case "bump": // GEOMETRICALLY EQUIVALENT TO - return rune(0x224e), true - case "bumpE": // EQUALS SIGN WITH BUMPY ABOVE - return rune(0x2aae), true - case "bumpe": // DIFFERENCE BETWEEN - return rune(0x224f), true - case "bumpeq": // DIFFERENCE BETWEEN - return rune(0x224f), true - case "burhar": // UPWARDS HARPOON WITH BARB RIGHT FROM BAR - return rune(0x295c), true + case "b.Delta": // MATHEMATICAL BOLD CAPITAL DELTA + return rune(0x01d6ab), true + case "b.Gamma": // MATHEMATICAL BOLD CAPITAL GAMMA + return rune(0x01d6aa), true + case "b.Gammad": // MATHEMATICAL BOLD CAPITAL DIGAMMA + return rune(0x01d7ca), true + case "b.Lambda": // MATHEMATICAL BOLD CAPITAL LAMDA + return rune(0x01d6b2), true + case "b.Omega": // MATHEMATICAL BOLD CAPITAL OMEGA + return rune(0x01d6c0), true + case "b.Phi": // MATHEMATICAL BOLD CAPITAL PHI + return rune(0x01d6bd), true + case "b.Pi": // MATHEMATICAL BOLD CAPITAL PI + return rune(0x01d6b7), true + case "b.Psi": // MATHEMATICAL BOLD CAPITAL PSI + return rune(0x01d6bf), true + case "b.Sigma": // MATHEMATICAL BOLD CAPITAL SIGMA + return rune(0x01d6ba), true + case "b.Theta": // MATHEMATICAL BOLD CAPITAL THETA + return rune(0x01d6af), true + case "b.Upsi": // MATHEMATICAL BOLD CAPITAL UPSILON + return rune(0x01d6bc), true + case "b.Xi": // MATHEMATICAL BOLD CAPITAL XI + return rune(0x01d6b5), true + case "b.alpha": // MATHEMATICAL BOLD SMALL ALPHA + return rune(0x01d6c2), true + case "b.beta": // MATHEMATICAL BOLD SMALL BETA + return rune(0x01d6c3), true + case "b.chi": // MATHEMATICAL BOLD SMALL CHI + return rune(0x01d6d8), true + case "b.delta": // MATHEMATICAL BOLD SMALL DELTA + return rune(0x01d6c5), true + case "b.epsi": // MATHEMATICAL BOLD SMALL EPSILON + return rune(0x01d6c6), true + case "b.epsiv": // MATHEMATICAL BOLD EPSILON SYMBOL + return rune(0x01d6dc), true + case "b.eta": // MATHEMATICAL BOLD SMALL ETA + return rune(0x01d6c8), true + case "b.gamma": // MATHEMATICAL BOLD SMALL GAMMA + return rune(0x01d6c4), true + case "b.gammad": // MATHEMATICAL BOLD SMALL DIGAMMA + return rune(0x01d7cb), true + case "b.iota": // MATHEMATICAL BOLD SMALL IOTA + return rune(0x01d6ca), true + case "b.kappa": // MATHEMATICAL BOLD SMALL KAPPA + return rune(0x01d6cb), true + case "b.kappav": // MATHEMATICAL BOLD KAPPA SYMBOL + return rune(0x01d6de), true + case "b.lambda": // MATHEMATICAL BOLD SMALL LAMDA + return rune(0x01d6cc), true + case "b.mu": // MATHEMATICAL BOLD SMALL MU + return rune(0x01d6cd), true + case "b.nu": // MATHEMATICAL BOLD SMALL NU + return rune(0x01d6ce), true + case "b.omega": // MATHEMATICAL BOLD SMALL OMEGA + return rune(0x01d6da), true + case "b.phi": // MATHEMATICAL BOLD SMALL PHI + return rune(0x01d6d7), true + case "b.phiv": // MATHEMATICAL BOLD PHI SYMBOL + return rune(0x01d6df), true + case "b.pi": // MATHEMATICAL BOLD SMALL PI + return rune(0x01d6d1), true + case "b.piv": // MATHEMATICAL BOLD PI SYMBOL + return rune(0x01d6e1), true + case "b.psi": // MATHEMATICAL BOLD SMALL PSI + return rune(0x01d6d9), true + case "b.rho": // MATHEMATICAL BOLD SMALL RHO + return rune(0x01d6d2), true + case "b.rhov": // MATHEMATICAL BOLD RHO SYMBOL + return rune(0x01d6e0), true + case "b.sigma": // MATHEMATICAL BOLD SMALL SIGMA + return rune(0x01d6d4), true + case "b.sigmav": // MATHEMATICAL BOLD SMALL FINAL SIGMA + return rune(0x01d6d3), true + case "b.tau": // MATHEMATICAL BOLD SMALL TAU + return rune(0x01d6d5), true + case "b.thetas": // MATHEMATICAL BOLD SMALL THETA + return rune(0x01d6c9), true + case "b.thetav": // MATHEMATICAL BOLD THETA SYMBOL + return rune(0x01d6dd), true + case "b.upsi": // MATHEMATICAL BOLD SMALL UPSILON + return rune(0x01d6d6), true + case "b.xi": // MATHEMATICAL BOLD SMALL XI + return rune(0x01d6cf), true + case "b.zeta": // MATHEMATICAL BOLD SMALL ZETA + return rune(0x01d6c7), true + case "bNot": // REVERSED DOUBLE STROKE NOT SIGN + return rune(0x2aed), true + case "backcong": // ALL EQUAL TO + return rune(0x224c), true + case "backepsilon": // GREEK REVERSED LUNATE EPSILON SYMBOL + return rune(0x03f6), true + case "backprime": // REVERSED PRIME + return rune(0x2035), true + case "backsim": // REVERSED TILDE + return rune(0x223d), true + case "backsimeq": // REVERSED TILDE EQUALS + return rune(0x22cd), true + case "barV": // DOUBLE DOWN TACK + return rune(0x2aea), true + case "barvee": // NOR + return rune(0x22bd), true + case "barwed": // PROJECTIVE + return rune(0x2305), true + case "barwedge": // PROJECTIVE + return rune(0x2305), true + case "bbrk": // BOTTOM SQUARE BRACKET + return rune(0x23b5), true + case "bbrktbrk": // BOTTOM SQUARE BRACKET OVER TOP SQUARE BRACKET + return rune(0x23b6), true + case "bcong": // ALL EQUAL TO + return rune(0x224c), true + case "bcy": // CYRILLIC SMALL LETTER BE + return rune(0x0431), true + case "bdlhar": // DOWNWARDS HARPOON WITH BARB LEFT FROM BAR + return rune(0x2961), true + case "bdquo": // DOUBLE LOW-9 QUOTATION MARK + return rune(0x201e), true + case "bdrhar": // DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR + return rune(0x295d), true + case "becaus": // BECAUSE + return rune(0x2235), true + case "because": // BECAUSE + return rune(0x2235), true + case "bemptyv": // REVERSED EMPTY SET + return rune(0x29b0), true + case "bepsi": // GREEK REVERSED LUNATE EPSILON SYMBOL + return rune(0x03f6), true + case "bernou": // SCRIPT CAPITAL B + return rune(0x212c), true + case "beta": // GREEK SMALL LETTER BETA + return rune(0x03b2), true + case "beth": // BET SYMBOL + return rune(0x2136), true + case "between": // BETWEEN + return rune(0x226c), true + case "bfr": // MATHEMATICAL FRAKTUR SMALL B + return rune(0x01d51f), true + case "bgr": // GREEK SMALL LETTER BETA + return rune(0x03b2), true + case "bigcap": // N-ARY INTERSECTION + return rune(0x22c2), true + case "bigcirc": // LARGE CIRCLE + return rune(0x25ef), true + case "bigcup": // N-ARY UNION + return rune(0x22c3), true + case "bigodot": // N-ARY CIRCLED DOT OPERATOR + return rune(0x2a00), true + case "bigoplus": // N-ARY CIRCLED PLUS OPERATOR + return rune(0x2a01), true + case "bigotimes": // N-ARY CIRCLED TIMES OPERATOR + return rune(0x2a02), true + case "bigsqcup": // N-ARY SQUARE UNION OPERATOR + return rune(0x2a06), true + case "bigstar": // BLACK STAR + return rune(0x2605), true + case "bigtriangledown": // WHITE DOWN-POINTING TRIANGLE + return rune(0x25bd), true + case "bigtriangleup": // WHITE UP-POINTING TRIANGLE + return rune(0x25b3), true + case "biguplus": // N-ARY UNION OPERATOR WITH PLUS + return rune(0x2a04), true + case "bigvee": // N-ARY LOGICAL OR + return rune(0x22c1), true + case "bigwedge": // N-ARY LOGICAL AND + return rune(0x22c0), true + case "bkarow": // RIGHTWARDS DOUBLE DASH ARROW + return rune(0x290d), true + case "blacklozenge": // BLACK LOZENGE + return rune(0x29eb), true + case "blacksquare": // BLACK SMALL SQUARE + return rune(0x25aa), true + case "blacktriangle": // BLACK UP-POINTING SMALL TRIANGLE + return rune(0x25b4), true + case "blacktriangledown": // BLACK DOWN-POINTING SMALL TRIANGLE + return rune(0x25be), true + case "blacktriangleleft": // BLACK LEFT-POINTING SMALL TRIANGLE + return rune(0x25c2), true + case "blacktriangleright": // BLACK RIGHT-POINTING SMALL TRIANGLE + return rune(0x25b8), true + case "blank": // BLANK SYMBOL + return rune(0x2422), true + case "bldhar": // LEFTWARDS HARPOON WITH BARB DOWN FROM BAR + return rune(0x295e), true + case "blk12": // MEDIUM SHADE + return rune(0x2592), true + case "blk14": // LIGHT SHADE + return rune(0x2591), true + case "blk34": // DARK SHADE + return rune(0x2593), true + case "block": // FULL BLOCK + return rune(0x2588), true + case "bluhar": // LEFTWARDS HARPOON WITH BARB UP FROM BAR + return rune(0x295a), true + case "bne": // EQUALS SIGN with reverse slash + return rune(0x3d), true + case "bnequiv": // IDENTICAL TO with reverse slash + return rune(0x2261), true + case "bnot": // REVERSED NOT SIGN + return rune(0x2310), true + case "bopf": // MATHEMATICAL DOUBLE-STRUCK SMALL B + return rune(0x01d553), true + case "bot": // UP TACK + return rune(0x22a5), true + case "bottom": // UP TACK + return rune(0x22a5), true + case "bowtie": // BOWTIE + return rune(0x22c8), true + case "boxDL": // BOX DRAWINGS DOUBLE DOWN AND LEFT + return rune(0x2557), true + case "boxDR": // BOX DRAWINGS DOUBLE DOWN AND RIGHT + return rune(0x2554), true + case "boxDl": // BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE + return rune(0x2556), true + case "boxDr": // BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE + return rune(0x2553), true + case "boxH": // BOX DRAWINGS DOUBLE HORIZONTAL + return rune(0x2550), true + case "boxHD": // BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL + return rune(0x2566), true + case "boxHU": // BOX DRAWINGS DOUBLE UP AND HORIZONTAL + return rune(0x2569), true + case "boxHd": // BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE + return rune(0x2564), true + case "boxHu": // BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE + return rune(0x2567), true + case "boxUL": // BOX DRAWINGS DOUBLE UP AND LEFT + return rune(0x255d), true + case "boxUR": // BOX DRAWINGS DOUBLE UP AND RIGHT + return rune(0x255a), true + case "boxUl": // BOX DRAWINGS UP DOUBLE AND LEFT SINGLE + return rune(0x255c), true + case "boxUr": // BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE + return rune(0x2559), true + case "boxV": // BOX DRAWINGS DOUBLE VERTICAL + return rune(0x2551), true + case "boxVH": // BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL + return rune(0x256c), true + case "boxVL": // BOX DRAWINGS DOUBLE VERTICAL AND LEFT + return rune(0x2563), true + case "boxVR": // BOX DRAWINGS DOUBLE VERTICAL AND RIGHT + return rune(0x2560), true + case "boxVh": // BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE + return rune(0x256b), true + case "boxVl": // BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE + return rune(0x2562), true + case "boxVr": // BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE + return rune(0x255f), true + case "boxbox": // TWO JOINED SQUARES + return rune(0x29c9), true + case "boxdL": // BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE + return rune(0x2555), true + case "boxdR": // BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE + return rune(0x2552), true + case "boxdl": // BOX DRAWINGS LIGHT DOWN AND LEFT + return rune(0x2510), true + case "boxdr": // BOX DRAWINGS LIGHT DOWN AND RIGHT + return rune(0x250c), true + case "boxh": // BOX DRAWINGS LIGHT HORIZONTAL + return rune(0x2500), true + case "boxhD": // BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE + return rune(0x2565), true + case "boxhU": // BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE + return rune(0x2568), true + case "boxhd": // BOX DRAWINGS LIGHT DOWN AND HORIZONTAL + return rune(0x252c), true + case "boxhu": // BOX DRAWINGS LIGHT UP AND HORIZONTAL + return rune(0x2534), true + case "boxminus": // SQUARED MINUS + return rune(0x229f), true + case "boxplus": // SQUARED PLUS + return rune(0x229e), true + case "boxtimes": // SQUARED TIMES + return rune(0x22a0), true + case "boxuL": // BOX DRAWINGS UP SINGLE AND LEFT DOUBLE + return rune(0x255b), true + case "boxuR": // BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE + return rune(0x2558), true + case "boxul": // BOX DRAWINGS LIGHT UP AND LEFT + return rune(0x2518), true + case "boxur": // BOX DRAWINGS LIGHT UP AND RIGHT + return rune(0x2514), true + case "boxv": // BOX DRAWINGS LIGHT VERTICAL + return rune(0x2502), true + case "boxvH": // BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE + return rune(0x256a), true + case "boxvL": // BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE + return rune(0x2561), true + case "boxvR": // BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE + return rune(0x255e), true + case "boxvh": // BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL + return rune(0x253c), true + case "boxvl": // BOX DRAWINGS LIGHT VERTICAL AND LEFT + return rune(0x2524), true + case "boxvr": // BOX DRAWINGS LIGHT VERTICAL AND RIGHT + return rune(0x251c), true + case "bprime": // REVERSED PRIME + return rune(0x2035), true + case "brdhar": // RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR + return rune(0x295f), true + case "breve": // BREVE + return rune(0x02d8), true + case "bruhar": // RIGHTWARDS HARPOON WITH BARB UP FROM BAR + return rune(0x295b), true + case "brvbar": // BROKEN BAR + return rune(0xa6), true + case "bscr": // MATHEMATICAL SCRIPT SMALL B + return rune(0x01d4b7), true + case "bsemi": // REVERSED SEMICOLON + return rune(0x204f), true + case "bsim": // REVERSED TILDE + return rune(0x223d), true + case "bsime": // REVERSED TILDE EQUALS + return rune(0x22cd), true + case "bsol": // REVERSE SOLIDUS + return rune(0x5c), true + case "bsolb": // SQUARED FALLING DIAGONAL SLASH + return rune(0x29c5), true + case "bsolhsub": // REVERSE SOLIDUS PRECEDING SUBSET + return rune(0x27c8), true + case "btimes": // SEMIDIRECT PRODUCT WITH BOTTOM CLOSED + return rune(0x2a32), true + case "bulhar": // UPWARDS HARPOON WITH BARB LEFT FROM BAR + return rune(0x2960), true + case "bull": // BULLET + return rune(0x2022), true + case "bullet": // BULLET + return rune(0x2022), true + case "bump": // GEOMETRICALLY EQUIVALENT TO + return rune(0x224e), true + case "bumpE": // EQUALS SIGN WITH BUMPY ABOVE + return rune(0x2aae), true + case "bumpe": // DIFFERENCE BETWEEN + return rune(0x224f), true + case "bumpeq": // DIFFERENCE BETWEEN + return rune(0x224f), true + case "burhar": // UPWARDS HARPOON WITH BARB RIGHT FROM BAR + return rune(0x295c), true } case 'c': switch name { - case "cacute": // LATIN SMALL LETTER C WITH ACUTE - return rune(0x0107), true - case "cap": // INTERSECTION - return rune(0x2229), true - case "capand": // INTERSECTION WITH LOGICAL AND - return rune(0x2a44), true - case "capbrcup": // INTERSECTION ABOVE BAR ABOVE UNION - return rune(0x2a49), true - case "capcap": // INTERSECTION BESIDE AND JOINED WITH INTERSECTION - return rune(0x2a4b), true - case "capcup": // INTERSECTION ABOVE UNION - return rune(0x2a47), true - case "capdot": // INTERSECTION WITH DOT - return rune(0x2a40), true - case "capint": // INTEGRAL WITH INTERSECTION - return rune(0x2a19), true - case "caps": // INTERSECTION with serifs - return rune(0x2229), true - case "caret": // CARET INSERTION POINT - return rune(0x2041), true - case "caron": // CARON - return rune(0x02c7), true - case "ccaps": // CLOSED INTERSECTION WITH SERIFS - return rune(0x2a4d), true - case "ccaron": // LATIN SMALL LETTER C WITH CARON - return rune(0x010d), true - case "ccedil": // LATIN SMALL LETTER C WITH CEDILLA - return rune(0xe7), true - case "ccirc": // LATIN SMALL LETTER C WITH CIRCUMFLEX - return rune(0x0109), true - case "ccups": // CLOSED UNION WITH SERIFS - return rune(0x2a4c), true - case "ccupssm": // CLOSED UNION WITH SERIFS AND SMASH PRODUCT - return rune(0x2a50), true - case "cdot": // LATIN SMALL LETTER C WITH DOT ABOVE - return rune(0x010b), true - case "cedil": // CEDILLA - return rune(0xb8), true - case "cemptyv": // EMPTY SET WITH SMALL CIRCLE ABOVE - return rune(0x29b2), true - case "cent": // CENT SIGN - return rune(0xa2), true - case "centerdot": // MIDDLE DOT - return rune(0xb7), true - case "cfr": // MATHEMATICAL FRAKTUR SMALL C - return rune(0x01d520), true - case "chcy": // CYRILLIC SMALL LETTER CHE - return rune(0x0447), true - case "check": // CHECK MARK - return rune(0x2713), true - case "checkmark": // CHECK MARK - return rune(0x2713), true - case "chi": // GREEK SMALL LETTER CHI - return rune(0x03c7), true - case "cir": // WHITE CIRCLE - return rune(0x25cb), true - case "cirE": // CIRCLE WITH TWO HORIZONTAL STROKES TO THE RIGHT - return rune(0x29c3), true - case "cirb": // SQUARED SMALL CIRCLE - return rune(0x29c7), true - case "circ": // MODIFIER LETTER CIRCUMFLEX ACCENT - return rune(0x02c6), true - case "circeq": // RING EQUAL TO - return rune(0x2257), true - case "circlearrowleft": // ANTICLOCKWISE OPEN CIRCLE ARROW - return rune(0x21ba), true - case "circlearrowright": // CLOCKWISE OPEN CIRCLE ARROW - return rune(0x21bb), true - case "circledR": // REGISTERED SIGN - return rune(0xae), true - case "circledS": // CIRCLED LATIN CAPITAL LETTER S - return rune(0x24c8), true - case "circledast": // CIRCLED ASTERISK OPERATOR - return rune(0x229b), true - case "circledcirc": // CIRCLED RING OPERATOR - return rune(0x229a), true - case "circleddash": // CIRCLED DASH - return rune(0x229d), true - case "cirdarr": // WHITE CIRCLE WITH DOWN ARROW - return rune(0x29ec), true - case "cire": // RING EQUAL TO - return rune(0x2257), true - case "cirerr": // ERROR-BARRED WHITE CIRCLE - return rune(0x29f2), true - case "cirfdarr": // BLACK CIRCLE WITH DOWN ARROW - return rune(0x29ed), true - case "cirferr": // ERROR-BARRED BLACK CIRCLE - return rune(0x29f3), true - case "cirfnint": // CIRCULATION FUNCTION - return rune(0x2a10), true - case "cirmid": // VERTICAL LINE WITH CIRCLE ABOVE - return rune(0x2aef), true - case "cirscir": // CIRCLE WITH SMALL CIRCLE TO THE RIGHT - return rune(0x29c2), true - case "closur": // CLOSE UP - return rune(0x2050), true - case "clubs": // BLACK CLUB SUIT - return rune(0x2663), true - case "clubsuit": // BLACK CLUB SUIT - return rune(0x2663), true - case "colon": // COLON - return rune(0x3a), true - case "colone": // COLON EQUALS - return rune(0x2254), true - case "coloneq": // COLON EQUALS - return rune(0x2254), true - case "comma": // COMMA - return rune(0x2c), true - case "commat": // COMMERCIAL AT - return rune(0x40), true - case "comp": // COMPLEMENT - return rune(0x2201), true - case "compfn": // RING OPERATOR - return rune(0x2218), true - case "complement": // COMPLEMENT - return rune(0x2201), true - case "complexes": // DOUBLE-STRUCK CAPITAL C - return rune(0x2102), true - case "cong": // APPROXIMATELY EQUAL TO - return rune(0x2245), true - case "congdot": // CONGRUENT WITH DOT ABOVE - return rune(0x2a6d), true - case "conint": // CONTOUR INTEGRAL - return rune(0x222e), true - case "copf": // MATHEMATICAL DOUBLE-STRUCK SMALL C - return rune(0x01d554), true - case "coprod": // N-ARY COPRODUCT - return rune(0x2210), true - case "copy": // COPYRIGHT SIGN - return rune(0xa9), true - case "copysr": // SOUND RECORDING COPYRIGHT - return rune(0x2117), true - case "crarr": // DOWNWARDS ARROW WITH CORNER LEFTWARDS - return rune(0x21b5), true - case "cross": // BALLOT X - return rune(0x2717), true - case "cscr": // MATHEMATICAL SCRIPT SMALL C - return rune(0x01d4b8), true - case "csub": // CLOSED SUBSET - return rune(0x2acf), true - case "csube": // CLOSED SUBSET OR EQUAL TO - return rune(0x2ad1), true - case "csup": // CLOSED SUPERSET - return rune(0x2ad0), true - case "csupe": // CLOSED SUPERSET OR EQUAL TO - return rune(0x2ad2), true - case "ctdot": // MIDLINE HORIZONTAL ELLIPSIS - return rune(0x22ef), true - case "cudarrl": // RIGHT-SIDE ARC CLOCKWISE ARROW - return rune(0x2938), true - case "cudarrr": // ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS - return rune(0x2935), true - case "cuepr": // EQUAL TO OR PRECEDES - return rune(0x22de), true - case "cuesc": // EQUAL TO OR SUCCEEDS - return rune(0x22df), true - case "cularr": // ANTICLOCKWISE TOP SEMICIRCLE ARROW - return rune(0x21b6), true - case "cularrp": // TOP ARC ANTICLOCKWISE ARROW WITH PLUS - return rune(0x293d), true - case "cup": // UNION - return rune(0x222a), true - case "cupbrcap": // UNION ABOVE BAR ABOVE INTERSECTION - return rune(0x2a48), true - case "cupcap": // UNION ABOVE INTERSECTION - return rune(0x2a46), true - case "cupcup": // UNION BESIDE AND JOINED WITH UNION - return rune(0x2a4a), true - case "cupdot": // MULTISET MULTIPLICATION - return rune(0x228d), true - case "cupint": // INTEGRAL WITH UNION - return rune(0x2a1a), true - case "cupor": // UNION WITH LOGICAL OR - return rune(0x2a45), true - case "cupre": // PRECEDES OR EQUAL TO - return rune(0x227c), true - case "cups": // UNION with serifs - return rune(0x222a), true - case "curarr": // CLOCKWISE TOP SEMICIRCLE ARROW - return rune(0x21b7), true - case "curarrm": // TOP ARC CLOCKWISE ARROW WITH MINUS - return rune(0x293c), true - case "curlyeqprec": // EQUAL TO OR PRECEDES - return rune(0x22de), true - case "curlyeqsucc": // EQUAL TO OR SUCCEEDS - return rune(0x22df), true - case "curlyvee": // CURLY LOGICAL OR - return rune(0x22ce), true - case "curlywedge": // CURLY LOGICAL AND - return rune(0x22cf), true - case "curren": // CURRENCY SIGN - return rune(0xa4), true - case "curvearrowleft": // ANTICLOCKWISE TOP SEMICIRCLE ARROW - return rune(0x21b6), true - case "curvearrowright": // CLOCKWISE TOP SEMICIRCLE ARROW - return rune(0x21b7), true - case "cuvee": // CURLY LOGICAL OR - return rune(0x22ce), true - case "cuwed": // CURLY LOGICAL AND - return rune(0x22cf), true - case "cwconint": // CLOCKWISE CONTOUR INTEGRAL - return rune(0x2232), true - case "cwint": // CLOCKWISE INTEGRAL - return rune(0x2231), true - case "cylcty": // CYLINDRICITY - return rune(0x232d), true + case "cacute": // LATIN SMALL LETTER C WITH ACUTE + return rune(0x0107), true + case "cap": // INTERSECTION + return rune(0x2229), true + case "capand": // INTERSECTION WITH LOGICAL AND + return rune(0x2a44), true + case "capbrcup": // INTERSECTION ABOVE BAR ABOVE UNION + return rune(0x2a49), true + case "capcap": // INTERSECTION BESIDE AND JOINED WITH INTERSECTION + return rune(0x2a4b), true + case "capcup": // INTERSECTION ABOVE UNION + return rune(0x2a47), true + case "capdot": // INTERSECTION WITH DOT + return rune(0x2a40), true + case "capint": // INTEGRAL WITH INTERSECTION + return rune(0x2a19), true + case "caps": // INTERSECTION with serifs + return rune(0x2229), true + case "caret": // CARET INSERTION POINT + return rune(0x2041), true + case "caron": // CARON + return rune(0x02c7), true + case "ccaps": // CLOSED INTERSECTION WITH SERIFS + return rune(0x2a4d), true + case "ccaron": // LATIN SMALL LETTER C WITH CARON + return rune(0x010d), true + case "ccedil": // LATIN SMALL LETTER C WITH CEDILLA + return rune(0xe7), true + case "ccirc": // LATIN SMALL LETTER C WITH CIRCUMFLEX + return rune(0x0109), true + case "ccups": // CLOSED UNION WITH SERIFS + return rune(0x2a4c), true + case "ccupssm": // CLOSED UNION WITH SERIFS AND SMASH PRODUCT + return rune(0x2a50), true + case "cdot": // LATIN SMALL LETTER C WITH DOT ABOVE + return rune(0x010b), true + case "cedil": // CEDILLA + return rune(0xb8), true + case "cemptyv": // EMPTY SET WITH SMALL CIRCLE ABOVE + return rune(0x29b2), true + case "cent": // CENT SIGN + return rune(0xa2), true + case "centerdot": // MIDDLE DOT + return rune(0xb7), true + case "cfr": // MATHEMATICAL FRAKTUR SMALL C + return rune(0x01d520), true + case "chcy": // CYRILLIC SMALL LETTER CHE + return rune(0x0447), true + case "check": // CHECK MARK + return rune(0x2713), true + case "checkmark": // CHECK MARK + return rune(0x2713), true + case "chi": // GREEK SMALL LETTER CHI + return rune(0x03c7), true + case "cir": // WHITE CIRCLE + return rune(0x25cb), true + case "cirE": // CIRCLE WITH TWO HORIZONTAL STROKES TO THE RIGHT + return rune(0x29c3), true + case "cirb": // SQUARED SMALL CIRCLE + return rune(0x29c7), true + case "circ": // MODIFIER LETTER CIRCUMFLEX ACCENT + return rune(0x02c6), true + case "circeq": // RING EQUAL TO + return rune(0x2257), true + case "circlearrowleft": // ANTICLOCKWISE OPEN CIRCLE ARROW + return rune(0x21ba), true + case "circlearrowright": // CLOCKWISE OPEN CIRCLE ARROW + return rune(0x21bb), true + case "circledR": // REGISTERED SIGN + return rune(0xae), true + case "circledS": // CIRCLED LATIN CAPITAL LETTER S + return rune(0x24c8), true + case "circledast": // CIRCLED ASTERISK OPERATOR + return rune(0x229b), true + case "circledcirc": // CIRCLED RING OPERATOR + return rune(0x229a), true + case "circleddash": // CIRCLED DASH + return rune(0x229d), true + case "cirdarr": // WHITE CIRCLE WITH DOWN ARROW + return rune(0x29ec), true + case "cire": // RING EQUAL TO + return rune(0x2257), true + case "cirerr": // ERROR-BARRED WHITE CIRCLE + return rune(0x29f2), true + case "cirfdarr": // BLACK CIRCLE WITH DOWN ARROW + return rune(0x29ed), true + case "cirferr": // ERROR-BARRED BLACK CIRCLE + return rune(0x29f3), true + case "cirfnint": // CIRCULATION FUNCTION + return rune(0x2a10), true + case "cirmid": // VERTICAL LINE WITH CIRCLE ABOVE + return rune(0x2aef), true + case "cirscir": // CIRCLE WITH SMALL CIRCLE TO THE RIGHT + return rune(0x29c2), true + case "closur": // CLOSE UP + return rune(0x2050), true + case "clubs": // BLACK CLUB SUIT + return rune(0x2663), true + case "clubsuit": // BLACK CLUB SUIT + return rune(0x2663), true + case "colon": // COLON + return rune(0x3a), true + case "colone": // COLON EQUALS + return rune(0x2254), true + case "coloneq": // COLON EQUALS + return rune(0x2254), true + case "comma": // COMMA + return rune(0x2c), true + case "commat": // COMMERCIAL AT + return rune(0x40), true + case "comp": // COMPLEMENT + return rune(0x2201), true + case "compfn": // RING OPERATOR + return rune(0x2218), true + case "complement": // COMPLEMENT + return rune(0x2201), true + case "complexes": // DOUBLE-STRUCK CAPITAL C + return rune(0x2102), true + case "cong": // APPROXIMATELY EQUAL TO + return rune(0x2245), true + case "congdot": // CONGRUENT WITH DOT ABOVE + return rune(0x2a6d), true + case "conint": // CONTOUR INTEGRAL + return rune(0x222e), true + case "copf": // MATHEMATICAL DOUBLE-STRUCK SMALL C + return rune(0x01d554), true + case "coprod": // N-ARY COPRODUCT + return rune(0x2210), true + case "copy": // COPYRIGHT SIGN + return rune(0xa9), true + case "copysr": // SOUND RECORDING COPYRIGHT + return rune(0x2117), true + case "crarr": // DOWNWARDS ARROW WITH CORNER LEFTWARDS + return rune(0x21b5), true + case "cross": // BALLOT X + return rune(0x2717), true + case "cscr": // MATHEMATICAL SCRIPT SMALL C + return rune(0x01d4b8), true + case "csub": // CLOSED SUBSET + return rune(0x2acf), true + case "csube": // CLOSED SUBSET OR EQUAL TO + return rune(0x2ad1), true + case "csup": // CLOSED SUPERSET + return rune(0x2ad0), true + case "csupe": // CLOSED SUPERSET OR EQUAL TO + return rune(0x2ad2), true + case "ctdot": // MIDLINE HORIZONTAL ELLIPSIS + return rune(0x22ef), true + case "cudarrl": // RIGHT-SIDE ARC CLOCKWISE ARROW + return rune(0x2938), true + case "cudarrr": // ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS + return rune(0x2935), true + case "cuepr": // EQUAL TO OR PRECEDES + return rune(0x22de), true + case "cuesc": // EQUAL TO OR SUCCEEDS + return rune(0x22df), true + case "cularr": // ANTICLOCKWISE TOP SEMICIRCLE ARROW + return rune(0x21b6), true + case "cularrp": // TOP ARC ANTICLOCKWISE ARROW WITH PLUS + return rune(0x293d), true + case "cup": // UNION + return rune(0x222a), true + case "cupbrcap": // UNION ABOVE BAR ABOVE INTERSECTION + return rune(0x2a48), true + case "cupcap": // UNION ABOVE INTERSECTION + return rune(0x2a46), true + case "cupcup": // UNION BESIDE AND JOINED WITH UNION + return rune(0x2a4a), true + case "cupdot": // MULTISET MULTIPLICATION + return rune(0x228d), true + case "cupint": // INTEGRAL WITH UNION + return rune(0x2a1a), true + case "cupor": // UNION WITH LOGICAL OR + return rune(0x2a45), true + case "cupre": // PRECEDES OR EQUAL TO + return rune(0x227c), true + case "cups": // UNION with serifs + return rune(0x222a), true + case "curarr": // CLOCKWISE TOP SEMICIRCLE ARROW + return rune(0x21b7), true + case "curarrm": // TOP ARC CLOCKWISE ARROW WITH MINUS + return rune(0x293c), true + case "curlyeqprec": // EQUAL TO OR PRECEDES + return rune(0x22de), true + case "curlyeqsucc": // EQUAL TO OR SUCCEEDS + return rune(0x22df), true + case "curlyvee": // CURLY LOGICAL OR + return rune(0x22ce), true + case "curlywedge": // CURLY LOGICAL AND + return rune(0x22cf), true + case "curren": // CURRENCY SIGN + return rune(0xa4), true + case "curvearrowleft": // ANTICLOCKWISE TOP SEMICIRCLE ARROW + return rune(0x21b6), true + case "curvearrowright": // CLOCKWISE TOP SEMICIRCLE ARROW + return rune(0x21b7), true + case "cuvee": // CURLY LOGICAL OR + return rune(0x22ce), true + case "cuwed": // CURLY LOGICAL AND + return rune(0x22cf), true + case "cwconint": // CLOCKWISE CONTOUR INTEGRAL + return rune(0x2232), true + case "cwint": // CLOCKWISE INTEGRAL + return rune(0x2231), true + case "cylcty": // CYLINDRICITY + return rune(0x232d), true } case 'd': switch name { - case "dAarr": // DOWNWARDS TRIPLE ARROW - return rune(0x290b), true - case "dArr": // DOWNWARDS DOUBLE ARROW - return rune(0x21d3), true - case "dHar": // DOWNWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT - return rune(0x2965), true - case "dagger": // DAGGER - return rune(0x2020), true - case "dalembrt": // SQUARE WITH CONTOURED OUTLINE - return rune(0x29e0), true - case "daleth": // DALET SYMBOL - return rune(0x2138), true - case "darr": // DOWNWARDS ARROW - return rune(0x2193), true - case "darr2": // DOWNWARDS PAIRED ARROWS - return rune(0x21ca), true - case "darrb": // DOWNWARDS ARROW TO BAR - return rune(0x2913), true - case "darrln": // DOWNWARDS ARROW WITH HORIZONTAL STROKE - return rune(0x2908), true - case "dash": // HYPHEN - return rune(0x2010), true - case "dashV": // DOUBLE VERTICAL BAR LEFT TURNSTILE - return rune(0x2ae3), true - case "dashv": // LEFT TACK - return rune(0x22a3), true - case "dbkarow": // RIGHTWARDS TRIPLE DASH ARROW - return rune(0x290f), true - case "dblac": // DOUBLE ACUTE ACCENT - return rune(0x02dd), true - case "dcaron": // LATIN SMALL LETTER D WITH CARON - return rune(0x010f), true - case "dcy": // CYRILLIC SMALL LETTER DE - return rune(0x0434), true - case "dd": // DOUBLE-STRUCK ITALIC SMALL D - return rune(0x2146), true - case "ddagger": // DOUBLE DAGGER - return rune(0x2021), true - case "ddarr": // DOWNWARDS PAIRED ARROWS - return rune(0x21ca), true - case "ddotseq": // EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW - return rune(0x2a77), true - case "deg": // DEGREE SIGN - return rune(0xb0), true - case "delta": // GREEK SMALL LETTER DELTA - return rune(0x03b4), true - case "demptyv": // EMPTY SET WITH OVERBAR - return rune(0x29b1), true - case "dfisht": // DOWN FISH TAIL - return rune(0x297f), true - case "dfr": // MATHEMATICAL FRAKTUR SMALL D - return rune(0x01d521), true - case "dgr": // GREEK SMALL LETTER DELTA - return rune(0x03b4), true - case "dharl": // DOWNWARDS HARPOON WITH BARB LEFTWARDS - return rune(0x21c3), true - case "dharr": // DOWNWARDS HARPOON WITH BARB RIGHTWARDS - return rune(0x21c2), true - case "diam": // DIAMOND OPERATOR - return rune(0x22c4), true - case "diamdarr": // BLACK DIAMOND WITH DOWN ARROW - return rune(0x29ea), true - case "diamerr": // ERROR-BARRED WHITE DIAMOND - return rune(0x29f0), true - case "diamerrf": // ERROR-BARRED BLACK DIAMOND - return rune(0x29f1), true - case "diamond": // DIAMOND OPERATOR - return rune(0x22c4), true - case "diamondsuit": // BLACK DIAMOND SUIT - return rune(0x2666), true - case "diams": // BLACK DIAMOND SUIT - return rune(0x2666), true - case "die": // DIAERESIS - return rune(0xa8), true - case "digamma": // GREEK SMALL LETTER DIGAMMA - return rune(0x03dd), true - case "disin": // ELEMENT OF WITH LONG HORIZONTAL STROKE - return rune(0x22f2), true - case "div": // DIVISION SIGN - return rune(0xf7), true - case "divide": // DIVISION SIGN - return rune(0xf7), true - case "divideontimes": // DIVISION TIMES - return rune(0x22c7), true - case "divonx": // DIVISION TIMES - return rune(0x22c7), true - case "djcy": // CYRILLIC SMALL LETTER DJE - return rune(0x0452), true - case "dlarr": // SOUTH WEST ARROW - return rune(0x2199), true - case "dlcorn": // BOTTOM LEFT CORNER - return rune(0x231e), true - case "dlcrop": // BOTTOM LEFT CROP - return rune(0x230d), true - case "dlharb": // DOWNWARDS HARPOON WITH BARB LEFT TO BAR - return rune(0x2959), true - case "dollar": // DOLLAR SIGN - return rune(0x24), true - case "dopf": // MATHEMATICAL DOUBLE-STRUCK SMALL D - return rune(0x01d555), true - case "dot": // DOT ABOVE - return rune(0x02d9), true - case "doteq": // APPROACHES THE LIMIT - return rune(0x2250), true - case "doteqdot": // GEOMETRICALLY EQUAL TO - return rune(0x2251), true - case "dotminus": // DOT MINUS - return rune(0x2238), true - case "dotplus": // DOT PLUS - return rune(0x2214), true - case "dotsquare": // SQUARED DOT OPERATOR - return rune(0x22a1), true - case "doublebarwedge": // PERSPECTIVE - return rune(0x2306), true - case "downarrow": // DOWNWARDS ARROW - return rune(0x2193), true - case "downdownarrows": // DOWNWARDS PAIRED ARROWS - return rune(0x21ca), true - case "downharpoonleft": // DOWNWARDS HARPOON WITH BARB LEFTWARDS - return rune(0x21c3), true - case "downharpoonright": // DOWNWARDS HARPOON WITH BARB RIGHTWARDS - return rune(0x21c2), true - case "drarr": // SOUTH EAST ARROW - return rune(0x2198), true - case "drbkarow": // RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW - return rune(0x2910), true - case "drcorn": // BOTTOM RIGHT CORNER - return rune(0x231f), true - case "drcrop": // BOTTOM RIGHT CROP - return rune(0x230c), true - case "drharb": // DOWNWARDS HARPOON WITH BARB RIGHT TO BAR - return rune(0x2955), true - case "dscr": // MATHEMATICAL SCRIPT SMALL D - return rune(0x01d4b9), true - case "dscy": // CYRILLIC SMALL LETTER DZE - return rune(0x0455), true - case "dsol": // SOLIDUS WITH OVERBAR - return rune(0x29f6), true - case "dstrok": // LATIN SMALL LETTER D WITH STROKE - return rune(0x0111), true - case "dtdot": // DOWN RIGHT DIAGONAL ELLIPSIS - return rune(0x22f1), true - case "dtri": // WHITE DOWN-POINTING SMALL TRIANGLE - return rune(0x25bf), true - case "dtrif": // BLACK DOWN-POINTING SMALL TRIANGLE - return rune(0x25be), true - case "dtrilf": // DOWN-POINTING TRIANGLE WITH LEFT HALF BLACK - return rune(0x29e8), true - case "dtrirf": // DOWN-POINTING TRIANGLE WITH RIGHT HALF BLACK - return rune(0x29e9), true - case "duarr": // DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW - return rune(0x21f5), true - case "duhar": // DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT - return rune(0x296f), true - case "dumap": // DOUBLE-ENDED MULTIMAP - return rune(0x29df), true - case "dwangle": // OBLIQUE ANGLE OPENING UP - return rune(0x29a6), true - case "dzcy": // CYRILLIC SMALL LETTER DZHE - return rune(0x045f), true - case "dzigrarr": // LONG RIGHTWARDS SQUIGGLE ARROW - return rune(0x27ff), true + case "dAarr": // DOWNWARDS TRIPLE ARROW + return rune(0x290b), true + case "dArr": // DOWNWARDS DOUBLE ARROW + return rune(0x21d3), true + case "dHar": // DOWNWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT + return rune(0x2965), true + case "dagger": // DAGGER + return rune(0x2020), true + case "dalembrt": // SQUARE WITH CONTOURED OUTLINE + return rune(0x29e0), true + case "daleth": // DALET SYMBOL + return rune(0x2138), true + case "darr": // DOWNWARDS ARROW + return rune(0x2193), true + case "darr2": // DOWNWARDS PAIRED ARROWS + return rune(0x21ca), true + case "darrb": // DOWNWARDS ARROW TO BAR + return rune(0x2913), true + case "darrln": // DOWNWARDS ARROW WITH HORIZONTAL STROKE + return rune(0x2908), true + case "dash": // HYPHEN + return rune(0x2010), true + case "dashV": // DOUBLE VERTICAL BAR LEFT TURNSTILE + return rune(0x2ae3), true + case "dashv": // LEFT TACK + return rune(0x22a3), true + case "dbkarow": // RIGHTWARDS TRIPLE DASH ARROW + return rune(0x290f), true + case "dblac": // DOUBLE ACUTE ACCENT + return rune(0x02dd), true + case "dcaron": // LATIN SMALL LETTER D WITH CARON + return rune(0x010f), true + case "dcy": // CYRILLIC SMALL LETTER DE + return rune(0x0434), true + case "dd": // DOUBLE-STRUCK ITALIC SMALL D + return rune(0x2146), true + case "ddagger": // DOUBLE DAGGER + return rune(0x2021), true + case "ddarr": // DOWNWARDS PAIRED ARROWS + return rune(0x21ca), true + case "ddotseq": // EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW + return rune(0x2a77), true + case "deg": // DEGREE SIGN + return rune(0xb0), true + case "delta": // GREEK SMALL LETTER DELTA + return rune(0x03b4), true + case "demptyv": // EMPTY SET WITH OVERBAR + return rune(0x29b1), true + case "dfisht": // DOWN FISH TAIL + return rune(0x297f), true + case "dfr": // MATHEMATICAL FRAKTUR SMALL D + return rune(0x01d521), true + case "dgr": // GREEK SMALL LETTER DELTA + return rune(0x03b4), true + case "dharl": // DOWNWARDS HARPOON WITH BARB LEFTWARDS + return rune(0x21c3), true + case "dharr": // DOWNWARDS HARPOON WITH BARB RIGHTWARDS + return rune(0x21c2), true + case "diam": // DIAMOND OPERATOR + return rune(0x22c4), true + case "diamdarr": // BLACK DIAMOND WITH DOWN ARROW + return rune(0x29ea), true + case "diamerr": // ERROR-BARRED WHITE DIAMOND + return rune(0x29f0), true + case "diamerrf": // ERROR-BARRED BLACK DIAMOND + return rune(0x29f1), true + case "diamond": // DIAMOND OPERATOR + return rune(0x22c4), true + case "diamondsuit": // BLACK DIAMOND SUIT + return rune(0x2666), true + case "diams": // BLACK DIAMOND SUIT + return rune(0x2666), true + case "die": // DIAERESIS + return rune(0xa8), true + case "digamma": // GREEK SMALL LETTER DIGAMMA + return rune(0x03dd), true + case "disin": // ELEMENT OF WITH LONG HORIZONTAL STROKE + return rune(0x22f2), true + case "div": // DIVISION SIGN + return rune(0xf7), true + case "divide": // DIVISION SIGN + return rune(0xf7), true + case "divideontimes": // DIVISION TIMES + return rune(0x22c7), true + case "divonx": // DIVISION TIMES + return rune(0x22c7), true + case "djcy": // CYRILLIC SMALL LETTER DJE + return rune(0x0452), true + case "dlarr": // SOUTH WEST ARROW + return rune(0x2199), true + case "dlcorn": // BOTTOM LEFT CORNER + return rune(0x231e), true + case "dlcrop": // BOTTOM LEFT CROP + return rune(0x230d), true + case "dlharb": // DOWNWARDS HARPOON WITH BARB LEFT TO BAR + return rune(0x2959), true + case "dollar": // DOLLAR SIGN + return rune(0x24), true + case "dopf": // MATHEMATICAL DOUBLE-STRUCK SMALL D + return rune(0x01d555), true + case "dot": // DOT ABOVE + return rune(0x02d9), true + case "doteq": // APPROACHES THE LIMIT + return rune(0x2250), true + case "doteqdot": // GEOMETRICALLY EQUAL TO + return rune(0x2251), true + case "dotminus": // DOT MINUS + return rune(0x2238), true + case "dotplus": // DOT PLUS + return rune(0x2214), true + case "dotsquare": // SQUARED DOT OPERATOR + return rune(0x22a1), true + case "doublebarwedge": // PERSPECTIVE + return rune(0x2306), true + case "downarrow": // DOWNWARDS ARROW + return rune(0x2193), true + case "downdownarrows": // DOWNWARDS PAIRED ARROWS + return rune(0x21ca), true + case "downharpoonleft": // DOWNWARDS HARPOON WITH BARB LEFTWARDS + return rune(0x21c3), true + case "downharpoonright": // DOWNWARDS HARPOON WITH BARB RIGHTWARDS + return rune(0x21c2), true + case "drarr": // SOUTH EAST ARROW + return rune(0x2198), true + case "drbkarow": // RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW + return rune(0x2910), true + case "drcorn": // BOTTOM RIGHT CORNER + return rune(0x231f), true + case "drcrop": // BOTTOM RIGHT CROP + return rune(0x230c), true + case "drharb": // DOWNWARDS HARPOON WITH BARB RIGHT TO BAR + return rune(0x2955), true + case "dscr": // MATHEMATICAL SCRIPT SMALL D + return rune(0x01d4b9), true + case "dscy": // CYRILLIC SMALL LETTER DZE + return rune(0x0455), true + case "dsol": // SOLIDUS WITH OVERBAR + return rune(0x29f6), true + case "dstrok": // LATIN SMALL LETTER D WITH STROKE + return rune(0x0111), true + case "dtdot": // DOWN RIGHT DIAGONAL ELLIPSIS + return rune(0x22f1), true + case "dtri": // WHITE DOWN-POINTING SMALL TRIANGLE + return rune(0x25bf), true + case "dtrif": // BLACK DOWN-POINTING SMALL TRIANGLE + return rune(0x25be), true + case "dtrilf": // DOWN-POINTING TRIANGLE WITH LEFT HALF BLACK + return rune(0x29e8), true + case "dtrirf": // DOWN-POINTING TRIANGLE WITH RIGHT HALF BLACK + return rune(0x29e9), true + case "duarr": // DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW + return rune(0x21f5), true + case "duhar": // DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT + return rune(0x296f), true + case "dumap": // DOUBLE-ENDED MULTIMAP + return rune(0x29df), true + case "dwangle": // OBLIQUE ANGLE OPENING UP + return rune(0x29a6), true + case "dzcy": // CYRILLIC SMALL LETTER DZHE + return rune(0x045f), true + case "dzigrarr": // LONG RIGHTWARDS SQUIGGLE ARROW + return rune(0x27ff), true } case 'e': switch name { - case "eDDot": // EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW - return rune(0x2a77), true - case "eDot": // GEOMETRICALLY EQUAL TO - return rune(0x2251), true - case "eacgr": // GREEK SMALL LETTER EPSILON WITH TONOS - return rune(0x03ad), true - case "eacute": // LATIN SMALL LETTER E WITH ACUTE - return rune(0xe9), true - case "easter": // EQUALS WITH ASTERISK - return rune(0x2a6e), true - case "ecaron": // LATIN SMALL LETTER E WITH CARON - return rune(0x011b), true - case "ecir": // RING IN EQUAL TO - return rune(0x2256), true - case "ecirc": // LATIN SMALL LETTER E WITH CIRCUMFLEX - return rune(0xea), true - case "ecolon": // EQUALS COLON - return rune(0x2255), true - case "ecy": // CYRILLIC SMALL LETTER E - return rune(0x044d), true - case "edot": // LATIN SMALL LETTER E WITH DOT ABOVE - return rune(0x0117), true - case "ee": // DOUBLE-STRUCK ITALIC SMALL E - return rune(0x2147), true - case "eeacgr": // GREEK SMALL LETTER ETA WITH TONOS - return rune(0x03ae), true - case "eegr": // GREEK SMALL LETTER ETA - return rune(0x03b7), true - case "efDot": // APPROXIMATELY EQUAL TO OR THE IMAGE OF - return rune(0x2252), true - case "efr": // MATHEMATICAL FRAKTUR SMALL E - return rune(0x01d522), true - case "eg": // DOUBLE-LINE EQUAL TO OR GREATER-THAN - return rune(0x2a9a), true - case "egr": // GREEK SMALL LETTER EPSILON - return rune(0x03b5), true - case "egrave": // LATIN SMALL LETTER E WITH GRAVE - return rune(0xe8), true - case "egs": // SLANTED EQUAL TO OR GREATER-THAN - return rune(0x2a96), true - case "egsdot": // SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE - return rune(0x2a98), true - case "el": // DOUBLE-LINE EQUAL TO OR LESS-THAN - return rune(0x2a99), true - case "elinters": // ELECTRICAL INTERSECTION - return rune(0x23e7), true - case "ell": // SCRIPT SMALL L - return rune(0x2113), true - case "els": // SLANTED EQUAL TO OR LESS-THAN - return rune(0x2a95), true - case "elsdot": // SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE - return rune(0x2a97), true - case "emacr": // LATIN SMALL LETTER E WITH MACRON - return rune(0x0113), true - case "empty": // EMPTY SET - return rune(0x2205), true - case "emptyset": // EMPTY SET - return rune(0x2205), true - case "emptyv": // EMPTY SET - return rune(0x2205), true - case "emsp": // EM SPACE - return rune(0x2003), true - case "emsp13": // THREE-PER-EM SPACE - return rune(0x2004), true - case "emsp14": // FOUR-PER-EM SPACE - return rune(0x2005), true - case "eng": // LATIN SMALL LETTER ENG - return rune(0x014b), true - case "ensp": // EN SPACE - return rune(0x2002), true - case "eogon": // LATIN SMALL LETTER E WITH OGONEK - return rune(0x0119), true - case "eopf": // MATHEMATICAL DOUBLE-STRUCK SMALL E - return rune(0x01d556), true - case "epar": // EQUAL AND PARALLEL TO - return rune(0x22d5), true - case "eparsl": // EQUALS SIGN AND SLANTED PARALLEL - return rune(0x29e3), true - case "eplus": // EQUALS SIGN ABOVE PLUS SIGN - return rune(0x2a71), true - case "epsi": // GREEK SMALL LETTER EPSILON - return rune(0x03b5), true - case "epsilon": // GREEK SMALL LETTER EPSILON - return rune(0x03b5), true - case "epsis": // GREEK LUNATE EPSILON SYMBOL - return rune(0x03f5), true - case "epsiv": // GREEK LUNATE EPSILON SYMBOL - return rune(0x03f5), true - case "eqcirc": // RING IN EQUAL TO - return rune(0x2256), true - case "eqcolon": // EQUALS COLON - return rune(0x2255), true - case "eqeq": // TWO CONSECUTIVE EQUALS SIGNS - return rune(0x2a75), true - case "eqsim": // MINUS TILDE - return rune(0x2242), true - case "eqslantgtr": // SLANTED EQUAL TO OR GREATER-THAN - return rune(0x2a96), true - case "eqslantless": // SLANTED EQUAL TO OR LESS-THAN - return rune(0x2a95), true - case "equals": // EQUALS SIGN - return rune(0x3d), true - case "equest": // QUESTIONED EQUAL TO - return rune(0x225f), true - case "equiv": // IDENTICAL TO - return rune(0x2261), true - case "equivDD": // EQUIVALENT WITH FOUR DOTS ABOVE - return rune(0x2a78), true - case "eqvparsl": // IDENTICAL TO AND SLANTED PARALLEL - return rune(0x29e5), true - case "erDot": // IMAGE OF OR APPROXIMATELY EQUAL TO - return rune(0x2253), true - case "erarr": // EQUALS SIGN ABOVE RIGHTWARDS ARROW - return rune(0x2971), true - case "escr": // SCRIPT SMALL E - return rune(0x212f), true - case "esdot": // APPROACHES THE LIMIT - return rune(0x2250), true - case "esim": // MINUS TILDE - return rune(0x2242), true - case "eta": // GREEK SMALL LETTER ETA - return rune(0x03b7), true - case "eth": // LATIN SMALL LETTER ETH - return rune(0xf0), true - case "euml": // LATIN SMALL LETTER E WITH DIAERESIS - return rune(0xeb), true - case "euro": // EURO SIGN - return rune(0x20ac), true - case "excl": // EXCLAMATION MARK - return rune(0x21), true - case "exist": // THERE EXISTS - return rune(0x2203), true - case "expectation": // SCRIPT CAPITAL E - return rune(0x2130), true - case "exponentiale": // DOUBLE-STRUCK ITALIC SMALL E - return rune(0x2147), true + case "eDDot": // EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW + return rune(0x2a77), true + case "eDot": // GEOMETRICALLY EQUAL TO + return rune(0x2251), true + case "eacgr": // GREEK SMALL LETTER EPSILON WITH TONOS + return rune(0x03ad), true + case "eacute": // LATIN SMALL LETTER E WITH ACUTE + return rune(0xe9), true + case "easter": // EQUALS WITH ASTERISK + return rune(0x2a6e), true + case "ecaron": // LATIN SMALL LETTER E WITH CARON + return rune(0x011b), true + case "ecir": // RING IN EQUAL TO + return rune(0x2256), true + case "ecirc": // LATIN SMALL LETTER E WITH CIRCUMFLEX + return rune(0xea), true + case "ecolon": // EQUALS COLON + return rune(0x2255), true + case "ecy": // CYRILLIC SMALL LETTER E + return rune(0x044d), true + case "edot": // LATIN SMALL LETTER E WITH DOT ABOVE + return rune(0x0117), true + case "ee": // DOUBLE-STRUCK ITALIC SMALL E + return rune(0x2147), true + case "eeacgr": // GREEK SMALL LETTER ETA WITH TONOS + return rune(0x03ae), true + case "eegr": // GREEK SMALL LETTER ETA + return rune(0x03b7), true + case "efDot": // APPROXIMATELY EQUAL TO OR THE IMAGE OF + return rune(0x2252), true + case "efr": // MATHEMATICAL FRAKTUR SMALL E + return rune(0x01d522), true + case "eg": // DOUBLE-LINE EQUAL TO OR GREATER-THAN + return rune(0x2a9a), true + case "egr": // GREEK SMALL LETTER EPSILON + return rune(0x03b5), true + case "egrave": // LATIN SMALL LETTER E WITH GRAVE + return rune(0xe8), true + case "egs": // SLANTED EQUAL TO OR GREATER-THAN + return rune(0x2a96), true + case "egsdot": // SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE + return rune(0x2a98), true + case "el": // DOUBLE-LINE EQUAL TO OR LESS-THAN + return rune(0x2a99), true + case "elinters": // ELECTRICAL INTERSECTION + return rune(0x23e7), true + case "ell": // SCRIPT SMALL L + return rune(0x2113), true + case "els": // SLANTED EQUAL TO OR LESS-THAN + return rune(0x2a95), true + case "elsdot": // SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE + return rune(0x2a97), true + case "emacr": // LATIN SMALL LETTER E WITH MACRON + return rune(0x0113), true + case "empty": // EMPTY SET + return rune(0x2205), true + case "emptyset": // EMPTY SET + return rune(0x2205), true + case "emptyv": // EMPTY SET + return rune(0x2205), true + case "emsp": // EM SPACE + return rune(0x2003), true + case "emsp13": // THREE-PER-EM SPACE + return rune(0x2004), true + case "emsp14": // FOUR-PER-EM SPACE + return rune(0x2005), true + case "eng": // LATIN SMALL LETTER ENG + return rune(0x014b), true + case "ensp": // EN SPACE + return rune(0x2002), true + case "eogon": // LATIN SMALL LETTER E WITH OGONEK + return rune(0x0119), true + case "eopf": // MATHEMATICAL DOUBLE-STRUCK SMALL E + return rune(0x01d556), true + case "epar": // EQUAL AND PARALLEL TO + return rune(0x22d5), true + case "eparsl": // EQUALS SIGN AND SLANTED PARALLEL + return rune(0x29e3), true + case "eplus": // EQUALS SIGN ABOVE PLUS SIGN + return rune(0x2a71), true + case "epsi": // GREEK SMALL LETTER EPSILON + return rune(0x03b5), true + case "epsilon": // GREEK SMALL LETTER EPSILON + return rune(0x03b5), true + case "epsis": // GREEK LUNATE EPSILON SYMBOL + return rune(0x03f5), true + case "epsiv": // GREEK LUNATE EPSILON SYMBOL + return rune(0x03f5), true + case "eqcirc": // RING IN EQUAL TO + return rune(0x2256), true + case "eqcolon": // EQUALS COLON + return rune(0x2255), true + case "eqeq": // TWO CONSECUTIVE EQUALS SIGNS + return rune(0x2a75), true + case "eqsim": // MINUS TILDE + return rune(0x2242), true + case "eqslantgtr": // SLANTED EQUAL TO OR GREATER-THAN + return rune(0x2a96), true + case "eqslantless": // SLANTED EQUAL TO OR LESS-THAN + return rune(0x2a95), true + case "equals": // EQUALS SIGN + return rune(0x3d), true + case "equest": // QUESTIONED EQUAL TO + return rune(0x225f), true + case "equiv": // IDENTICAL TO + return rune(0x2261), true + case "equivDD": // EQUIVALENT WITH FOUR DOTS ABOVE + return rune(0x2a78), true + case "eqvparsl": // IDENTICAL TO AND SLANTED PARALLEL + return rune(0x29e5), true + case "erDot": // IMAGE OF OR APPROXIMATELY EQUAL TO + return rune(0x2253), true + case "erarr": // EQUALS SIGN ABOVE RIGHTWARDS ARROW + return rune(0x2971), true + case "escr": // SCRIPT SMALL E + return rune(0x212f), true + case "esdot": // APPROACHES THE LIMIT + return rune(0x2250), true + case "esim": // MINUS TILDE + return rune(0x2242), true + case "eta": // GREEK SMALL LETTER ETA + return rune(0x03b7), true + case "eth": // LATIN SMALL LETTER ETH + return rune(0xf0), true + case "euml": // LATIN SMALL LETTER E WITH DIAERESIS + return rune(0xeb), true + case "euro": // EURO SIGN + return rune(0x20ac), true + case "excl": // EXCLAMATION MARK + return rune(0x21), true + case "exist": // THERE EXISTS + return rune(0x2203), true + case "expectation": // SCRIPT CAPITAL E + return rune(0x2130), true + case "exponentiale": // DOUBLE-STRUCK ITALIC SMALL E + return rune(0x2147), true } case 'f': switch name { - case "fallingdotseq": // APPROXIMATELY EQUAL TO OR THE IMAGE OF - return rune(0x2252), true - case "fbowtie": // BLACK BOWTIE - return rune(0x29d3), true - case "fcy": // CYRILLIC SMALL LETTER EF - return rune(0x0444), true - case "fdiag": // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT - return rune(0x2572), true - case "fdiordi": // FALLING DIAGONAL CROSSING RISING DIAGONAL - return rune(0x292c), true - case "fdonearr": // FALLING DIAGONAL CROSSING NORTH EAST ARROW - return rune(0x292f), true - case "female": // FEMALE SIGN - return rune(0x2640), true - case "ffilig": // LATIN SMALL LIGATURE FFI - return rune(0xfb03), true - case "fflig": // LATIN SMALL LIGATURE FF - return rune(0xfb00), true - case "ffllig": // LATIN SMALL LIGATURE FFL - return rune(0xfb04), true - case "ffr": // MATHEMATICAL FRAKTUR SMALL F - return rune(0x01d523), true - case "fhrglass": // BLACK HOURGLASS - return rune(0x29d7), true - case "filig": // LATIN SMALL LIGATURE FI - return rune(0xfb01), true - case "fjlig": // fj ligature - return rune(0x66), true - case "flat": // MUSIC FLAT SIGN - return rune(0x266d), true - case "fllig": // LATIN SMALL LIGATURE FL - return rune(0xfb02), true - case "fltns": // WHITE PARALLELOGRAM - return rune(0x25b1), true - case "fnof": // LATIN SMALL LETTER F WITH HOOK - return rune(0x0192), true - case "fopf": // MATHEMATICAL DOUBLE-STRUCK SMALL F - return rune(0x01d557), true - case "forall": // FOR ALL - return rune(0x2200), true - case "fork": // PITCHFORK - return rune(0x22d4), true - case "forkv": // ELEMENT OF OPENING DOWNWARDS - return rune(0x2ad9), true - case "fpartint": // FINITE PART INTEGRAL - return rune(0x2a0d), true - case "frac12": // VULGAR FRACTION ONE HALF - return rune(0xbd), true - case "frac13": // VULGAR FRACTION ONE THIRD - return rune(0x2153), true - case "frac14": // VULGAR FRACTION ONE QUARTER - return rune(0xbc), true - case "frac15": // VULGAR FRACTION ONE FIFTH - return rune(0x2155), true - case "frac16": // VULGAR FRACTION ONE SIXTH - return rune(0x2159), true - case "frac18": // VULGAR FRACTION ONE EIGHTH - return rune(0x215b), true - case "frac23": // VULGAR FRACTION TWO THIRDS - return rune(0x2154), true - case "frac25": // VULGAR FRACTION TWO FIFTHS - return rune(0x2156), true - case "frac34": // VULGAR FRACTION THREE QUARTERS - return rune(0xbe), true - case "frac35": // VULGAR FRACTION THREE FIFTHS - return rune(0x2157), true - case "frac38": // VULGAR FRACTION THREE EIGHTHS - return rune(0x215c), true - case "frac45": // VULGAR FRACTION FOUR FIFTHS - return rune(0x2158), true - case "frac56": // VULGAR FRACTION FIVE SIXTHS - return rune(0x215a), true - case "frac58": // VULGAR FRACTION FIVE EIGHTHS - return rune(0x215d), true - case "frac78": // VULGAR FRACTION SEVEN EIGHTHS - return rune(0x215e), true - case "frasl": // FRACTION SLASH - return rune(0x2044), true - case "frown": // FROWN - return rune(0x2322), true - case "fscr": // MATHEMATICAL SCRIPT SMALL F - return rune(0x01d4bb), true + case "fallingdotseq": // APPROXIMATELY EQUAL TO OR THE IMAGE OF + return rune(0x2252), true + case "fbowtie": // BLACK BOWTIE + return rune(0x29d3), true + case "fcy": // CYRILLIC SMALL LETTER EF + return rune(0x0444), true + case "fdiag": // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT + return rune(0x2572), true + case "fdiordi": // FALLING DIAGONAL CROSSING RISING DIAGONAL + return rune(0x292c), true + case "fdonearr": // FALLING DIAGONAL CROSSING NORTH EAST ARROW + return rune(0x292f), true + case "female": // FEMALE SIGN + return rune(0x2640), true + case "ffilig": // LATIN SMALL LIGATURE FFI + return rune(0xfb03), true + case "fflig": // LATIN SMALL LIGATURE FF + return rune(0xfb00), true + case "ffllig": // LATIN SMALL LIGATURE FFL + return rune(0xfb04), true + case "ffr": // MATHEMATICAL FRAKTUR SMALL F + return rune(0x01d523), true + case "fhrglass": // BLACK HOURGLASS + return rune(0x29d7), true + case "filig": // LATIN SMALL LIGATURE FI + return rune(0xfb01), true + case "fjlig": // fj ligature + return rune(0x66), true + case "flat": // MUSIC FLAT SIGN + return rune(0x266d), true + case "fllig": // LATIN SMALL LIGATURE FL + return rune(0xfb02), true + case "fltns": // WHITE PARALLELOGRAM + return rune(0x25b1), true + case "fnof": // LATIN SMALL LETTER F WITH HOOK + return rune(0x0192), true + case "fopf": // MATHEMATICAL DOUBLE-STRUCK SMALL F + return rune(0x01d557), true + case "forall": // FOR ALL + return rune(0x2200), true + case "fork": // PITCHFORK + return rune(0x22d4), true + case "forkv": // ELEMENT OF OPENING DOWNWARDS + return rune(0x2ad9), true + case "fpartint": // FINITE PART INTEGRAL + return rune(0x2a0d), true + case "frac12": // VULGAR FRACTION ONE HALF + return rune(0xbd), true + case "frac13": // VULGAR FRACTION ONE THIRD + return rune(0x2153), true + case "frac14": // VULGAR FRACTION ONE QUARTER + return rune(0xbc), true + case "frac15": // VULGAR FRACTION ONE FIFTH + return rune(0x2155), true + case "frac16": // VULGAR FRACTION ONE SIXTH + return rune(0x2159), true + case "frac18": // VULGAR FRACTION ONE EIGHTH + return rune(0x215b), true + case "frac23": // VULGAR FRACTION TWO THIRDS + return rune(0x2154), true + case "frac25": // VULGAR FRACTION TWO FIFTHS + return rune(0x2156), true + case "frac34": // VULGAR FRACTION THREE QUARTERS + return rune(0xbe), true + case "frac35": // VULGAR FRACTION THREE FIFTHS + return rune(0x2157), true + case "frac38": // VULGAR FRACTION THREE EIGHTHS + return rune(0x215c), true + case "frac45": // VULGAR FRACTION FOUR FIFTHS + return rune(0x2158), true + case "frac56": // VULGAR FRACTION FIVE SIXTHS + return rune(0x215a), true + case "frac58": // VULGAR FRACTION FIVE EIGHTHS + return rune(0x215d), true + case "frac78": // VULGAR FRACTION SEVEN EIGHTHS + return rune(0x215e), true + case "frasl": // FRACTION SLASH + return rune(0x2044), true + case "frown": // FROWN + return rune(0x2322), true + case "fscr": // MATHEMATICAL SCRIPT SMALL F + return rune(0x01d4bb), true } case 'g': switch name { - case "gE": // GREATER-THAN OVER EQUAL TO - return rune(0x2267), true - case "gEl": // GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN - return rune(0x2a8c), true - case "gacute": // LATIN SMALL LETTER G WITH ACUTE - return rune(0x01f5), true - case "gamma": // GREEK SMALL LETTER GAMMA - return rune(0x03b3), true - case "gammad": // GREEK SMALL LETTER DIGAMMA - return rune(0x03dd), true - case "gap": // GREATER-THAN OR APPROXIMATE - return rune(0x2a86), true - case "gbreve": // LATIN SMALL LETTER G WITH BREVE - return rune(0x011f), true - case "gcedil": // LATIN SMALL LETTER G WITH CEDILLA - return rune(0x0123), true - case "gcirc": // LATIN SMALL LETTER G WITH CIRCUMFLEX - return rune(0x011d), true - case "gcy": // CYRILLIC SMALL LETTER GHE - return rune(0x0433), true - case "gdot": // LATIN SMALL LETTER G WITH DOT ABOVE - return rune(0x0121), true - case "ge": // GREATER-THAN OR EQUAL TO - return rune(0x2265), true - case "gel": // GREATER-THAN EQUAL TO OR LESS-THAN - return rune(0x22db), true - case "geq": // GREATER-THAN OR EQUAL TO - return rune(0x2265), true - case "geqq": // GREATER-THAN OVER EQUAL TO - return rune(0x2267), true - case "geqslant": // GREATER-THAN OR SLANTED EQUAL TO - return rune(0x2a7e), true - case "ges": // GREATER-THAN OR SLANTED EQUAL TO - return rune(0x2a7e), true - case "gescc": // GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL - return rune(0x2aa9), true - case "gesdot": // GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE - return rune(0x2a80), true - case "gesdoto": // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE - return rune(0x2a82), true - case "gesdotol": // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT - return rune(0x2a84), true - case "gesl": // GREATER-THAN slanted EQUAL TO OR LESS-THAN - return rune(0x22db), true - case "gesles": // GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL - return rune(0x2a94), true - case "gfr": // MATHEMATICAL FRAKTUR SMALL G - return rune(0x01d524), true - case "gg": // MUCH GREATER-THAN - return rune(0x226b), true - case "ggg": // VERY MUCH GREATER-THAN - return rune(0x22d9), true - case "ggr": // GREEK SMALL LETTER GAMMA - return rune(0x03b3), true - case "gimel": // GIMEL SYMBOL - return rune(0x2137), true - case "gjcy": // CYRILLIC SMALL LETTER GJE - return rune(0x0453), true - case "gl": // GREATER-THAN OR LESS-THAN - return rune(0x2277), true - case "glE": // GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL - return rune(0x2a92), true - case "gla": // GREATER-THAN BESIDE LESS-THAN - return rune(0x2aa5), true - case "glj": // GREATER-THAN OVERLAPPING LESS-THAN - return rune(0x2aa4), true - case "gnE": // GREATER-THAN BUT NOT EQUAL TO - return rune(0x2269), true - case "gnap": // GREATER-THAN AND NOT APPROXIMATE - return rune(0x2a8a), true - case "gnapprox": // GREATER-THAN AND NOT APPROXIMATE - return rune(0x2a8a), true - case "gne": // GREATER-THAN AND SINGLE-LINE NOT EQUAL TO - return rune(0x2a88), true - case "gneq": // GREATER-THAN AND SINGLE-LINE NOT EQUAL TO - return rune(0x2a88), true - case "gneqq": // GREATER-THAN BUT NOT EQUAL TO - return rune(0x2269), true - case "gnsim": // GREATER-THAN BUT NOT EQUIVALENT TO - return rune(0x22e7), true - case "gopf": // MATHEMATICAL DOUBLE-STRUCK SMALL G - return rune(0x01d558), true - case "grave": // GRAVE ACCENT - return rune(0x60), true - case "gscr": // SCRIPT SMALL G - return rune(0x210a), true - case "gsdot": // GREATER-THAN WITH DOT - return rune(0x22d7), true - case "gsim": // GREATER-THAN OR EQUIVALENT TO - return rune(0x2273), true - case "gsime": // GREATER-THAN ABOVE SIMILAR OR EQUAL - return rune(0x2a8e), true - case "gsiml": // GREATER-THAN ABOVE SIMILAR ABOVE LESS-THAN - return rune(0x2a90), true - case "gt": // GREATER-THAN SIGN - return rune(0x3e), true - case "gtcc": // GREATER-THAN CLOSED BY CURVE - return rune(0x2aa7), true - case "gtcir": // GREATER-THAN WITH CIRCLE INSIDE - return rune(0x2a7a), true - case "gtdot": // GREATER-THAN WITH DOT - return rune(0x22d7), true - case "gtlPar": // DOUBLE LEFT ARC GREATER-THAN BRACKET - return rune(0x2995), true - case "gtquest": // GREATER-THAN WITH QUESTION MARK ABOVE - return rune(0x2a7c), true - case "gtrapprox": // GREATER-THAN OR APPROXIMATE - return rune(0x2a86), true - case "gtrarr": // GREATER-THAN ABOVE RIGHTWARDS ARROW - return rune(0x2978), true - case "gtrdot": // GREATER-THAN WITH DOT - return rune(0x22d7), true - case "gtreqless": // GREATER-THAN EQUAL TO OR LESS-THAN - return rune(0x22db), true - case "gtreqqless": // GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN - return rune(0x2a8c), true - case "gtrless": // GREATER-THAN OR LESS-THAN - return rune(0x2277), true - case "gtrpar": // SPHERICAL ANGLE OPENING LEFT - return rune(0x29a0), true - case "gtrsim": // GREATER-THAN OR EQUIVALENT TO - return rune(0x2273), true - case "gvertneqq": // GREATER-THAN BUT NOT EQUAL TO - with vertical stroke - return rune(0x2269), true - case "gvnE": // GREATER-THAN BUT NOT EQUAL TO - with vertical stroke - return rune(0x2269), true + case "gE": // GREATER-THAN OVER EQUAL TO + return rune(0x2267), true + case "gEl": // GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN + return rune(0x2a8c), true + case "gacute": // LATIN SMALL LETTER G WITH ACUTE + return rune(0x01f5), true + case "gamma": // GREEK SMALL LETTER GAMMA + return rune(0x03b3), true + case "gammad": // GREEK SMALL LETTER DIGAMMA + return rune(0x03dd), true + case "gap": // GREATER-THAN OR APPROXIMATE + return rune(0x2a86), true + case "gbreve": // LATIN SMALL LETTER G WITH BREVE + return rune(0x011f), true + case "gcedil": // LATIN SMALL LETTER G WITH CEDILLA + return rune(0x0123), true + case "gcirc": // LATIN SMALL LETTER G WITH CIRCUMFLEX + return rune(0x011d), true + case "gcy": // CYRILLIC SMALL LETTER GHE + return rune(0x0433), true + case "gdot": // LATIN SMALL LETTER G WITH DOT ABOVE + return rune(0x0121), true + case "ge": // GREATER-THAN OR EQUAL TO + return rune(0x2265), true + case "gel": // GREATER-THAN EQUAL TO OR LESS-THAN + return rune(0x22db), true + case "geq": // GREATER-THAN OR EQUAL TO + return rune(0x2265), true + case "geqq": // GREATER-THAN OVER EQUAL TO + return rune(0x2267), true + case "geqslant": // GREATER-THAN OR SLANTED EQUAL TO + return rune(0x2a7e), true + case "ges": // GREATER-THAN OR SLANTED EQUAL TO + return rune(0x2a7e), true + case "gescc": // GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL + return rune(0x2aa9), true + case "gesdot": // GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE + return rune(0x2a80), true + case "gesdoto": // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE + return rune(0x2a82), true + case "gesdotol": // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT + return rune(0x2a84), true + case "gesl": // GREATER-THAN slanted EQUAL TO OR LESS-THAN + return rune(0x22db), true + case "gesles": // GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL + return rune(0x2a94), true + case "gfr": // MATHEMATICAL FRAKTUR SMALL G + return rune(0x01d524), true + case "gg": // MUCH GREATER-THAN + return rune(0x226b), true + case "ggg": // VERY MUCH GREATER-THAN + return rune(0x22d9), true + case "ggr": // GREEK SMALL LETTER GAMMA + return rune(0x03b3), true + case "gimel": // GIMEL SYMBOL + return rune(0x2137), true + case "gjcy": // CYRILLIC SMALL LETTER GJE + return rune(0x0453), true + case "gl": // GREATER-THAN OR LESS-THAN + return rune(0x2277), true + case "glE": // GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL + return rune(0x2a92), true + case "gla": // GREATER-THAN BESIDE LESS-THAN + return rune(0x2aa5), true + case "glj": // GREATER-THAN OVERLAPPING LESS-THAN + return rune(0x2aa4), true + case "gnE": // GREATER-THAN BUT NOT EQUAL TO + return rune(0x2269), true + case "gnap": // GREATER-THAN AND NOT APPROXIMATE + return rune(0x2a8a), true + case "gnapprox": // GREATER-THAN AND NOT APPROXIMATE + return rune(0x2a8a), true + case "gne": // GREATER-THAN AND SINGLE-LINE NOT EQUAL TO + return rune(0x2a88), true + case "gneq": // GREATER-THAN AND SINGLE-LINE NOT EQUAL TO + return rune(0x2a88), true + case "gneqq": // GREATER-THAN BUT NOT EQUAL TO + return rune(0x2269), true + case "gnsim": // GREATER-THAN BUT NOT EQUIVALENT TO + return rune(0x22e7), true + case "gopf": // MATHEMATICAL DOUBLE-STRUCK SMALL G + return rune(0x01d558), true + case "grave": // GRAVE ACCENT + return rune(0x60), true + case "gscr": // SCRIPT SMALL G + return rune(0x210a), true + case "gsdot": // GREATER-THAN WITH DOT + return rune(0x22d7), true + case "gsim": // GREATER-THAN OR EQUIVALENT TO + return rune(0x2273), true + case "gsime": // GREATER-THAN ABOVE SIMILAR OR EQUAL + return rune(0x2a8e), true + case "gsiml": // GREATER-THAN ABOVE SIMILAR ABOVE LESS-THAN + return rune(0x2a90), true + case "gt": // GREATER-THAN SIGN + return rune(0x3e), true + case "gtcc": // GREATER-THAN CLOSED BY CURVE + return rune(0x2aa7), true + case "gtcir": // GREATER-THAN WITH CIRCLE INSIDE + return rune(0x2a7a), true + case "gtdot": // GREATER-THAN WITH DOT + return rune(0x22d7), true + case "gtlPar": // DOUBLE LEFT ARC GREATER-THAN BRACKET + return rune(0x2995), true + case "gtquest": // GREATER-THAN WITH QUESTION MARK ABOVE + return rune(0x2a7c), true + case "gtrapprox": // GREATER-THAN OR APPROXIMATE + return rune(0x2a86), true + case "gtrarr": // GREATER-THAN ABOVE RIGHTWARDS ARROW + return rune(0x2978), true + case "gtrdot": // GREATER-THAN WITH DOT + return rune(0x22d7), true + case "gtreqless": // GREATER-THAN EQUAL TO OR LESS-THAN + return rune(0x22db), true + case "gtreqqless": // GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN + return rune(0x2a8c), true + case "gtrless": // GREATER-THAN OR LESS-THAN + return rune(0x2277), true + case "gtrpar": // SPHERICAL ANGLE OPENING LEFT + return rune(0x29a0), true + case "gtrsim": // GREATER-THAN OR EQUIVALENT TO + return rune(0x2273), true + case "gvertneqq": // GREATER-THAN BUT NOT EQUAL TO - with vertical stroke + return rune(0x2269), true + case "gvnE": // GREATER-THAN BUT NOT EQUAL TO - with vertical stroke + return rune(0x2269), true } case 'h': switch name { - case "hArr": // LEFT RIGHT DOUBLE ARROW - return rune(0x21d4), true - case "hairsp": // HAIR SPACE - return rune(0x200a), true - case "half": // VULGAR FRACTION ONE HALF - return rune(0xbd), true - case "hamilt": // SCRIPT CAPITAL H - return rune(0x210b), true - case "hardcy": // CYRILLIC SMALL LETTER HARD SIGN - return rune(0x044a), true - case "harr": // LEFT RIGHT ARROW - return rune(0x2194), true - case "harrcir": // LEFT RIGHT ARROW THROUGH SMALL CIRCLE - return rune(0x2948), true - case "harrw": // LEFT RIGHT WAVE ARROW - return rune(0x21ad), true - case "hbar": // PLANCK CONSTANT OVER TWO PI - return rune(0x210f), true - case "hcirc": // LATIN SMALL LETTER H WITH CIRCUMFLEX - return rune(0x0125), true - case "hearts": // BLACK HEART SUIT - return rune(0x2665), true - case "heartsuit": // BLACK HEART SUIT - return rune(0x2665), true - case "hellip": // HORIZONTAL ELLIPSIS - return rune(0x2026), true - case "hercon": // HERMITIAN CONJUGATE MATRIX - return rune(0x22b9), true - case "hfr": // MATHEMATICAL FRAKTUR SMALL H - return rune(0x01d525), true - case "hksearow": // SOUTH EAST ARROW WITH HOOK - return rune(0x2925), true - case "hkswarow": // SOUTH WEST ARROW WITH HOOK - return rune(0x2926), true - case "hoarr": // LEFT RIGHT OPEN-HEADED ARROW - return rune(0x21ff), true - case "homtht": // HOMOTHETIC - return rune(0x223b), true - case "hookleftarrow": // LEFTWARDS ARROW WITH HOOK - return rune(0x21a9), true - case "hookrightarrow": // RIGHTWARDS ARROW WITH HOOK - return rune(0x21aa), true - case "hopf": // MATHEMATICAL DOUBLE-STRUCK SMALL H - return rune(0x01d559), true - case "horbar": // HORIZONTAL BAR - return rune(0x2015), true - case "hrglass": // WHITE HOURGLASS - return rune(0x29d6), true - case "hscr": // MATHEMATICAL SCRIPT SMALL H - return rune(0x01d4bd), true - case "hslash": // PLANCK CONSTANT OVER TWO PI - return rune(0x210f), true - case "hstrok": // LATIN SMALL LETTER H WITH STROKE - return rune(0x0127), true - case "htimes": // VECTOR OR CROSS PRODUCT - return rune(0x2a2f), true - case "hybull": // HYPHEN BULLET - return rune(0x2043), true - case "hyphen": // HYPHEN - return rune(0x2010), true + case "hArr": // LEFT RIGHT DOUBLE ARROW + return rune(0x21d4), true + case "hairsp": // HAIR SPACE + return rune(0x200a), true + case "half": // VULGAR FRACTION ONE HALF + return rune(0xbd), true + case "hamilt": // SCRIPT CAPITAL H + return rune(0x210b), true + case "hardcy": // CYRILLIC SMALL LETTER HARD SIGN + return rune(0x044a), true + case "harr": // LEFT RIGHT ARROW + return rune(0x2194), true + case "harrcir": // LEFT RIGHT ARROW THROUGH SMALL CIRCLE + return rune(0x2948), true + case "harrw": // LEFT RIGHT WAVE ARROW + return rune(0x21ad), true + case "hbar": // PLANCK CONSTANT OVER TWO PI + return rune(0x210f), true + case "hcirc": // LATIN SMALL LETTER H WITH CIRCUMFLEX + return rune(0x0125), true + case "hearts": // BLACK HEART SUIT + return rune(0x2665), true + case "heartsuit": // BLACK HEART SUIT + return rune(0x2665), true + case "hellip": // HORIZONTAL ELLIPSIS + return rune(0x2026), true + case "hercon": // HERMITIAN CONJUGATE MATRIX + return rune(0x22b9), true + case "hfr": // MATHEMATICAL FRAKTUR SMALL H + return rune(0x01d525), true + case "hksearow": // SOUTH EAST ARROW WITH HOOK + return rune(0x2925), true + case "hkswarow": // SOUTH WEST ARROW WITH HOOK + return rune(0x2926), true + case "hoarr": // LEFT RIGHT OPEN-HEADED ARROW + return rune(0x21ff), true + case "homtht": // HOMOTHETIC + return rune(0x223b), true + case "hookleftarrow": // LEFTWARDS ARROW WITH HOOK + return rune(0x21a9), true + case "hookrightarrow": // RIGHTWARDS ARROW WITH HOOK + return rune(0x21aa), true + case "hopf": // MATHEMATICAL DOUBLE-STRUCK SMALL H + return rune(0x01d559), true + case "horbar": // HORIZONTAL BAR + return rune(0x2015), true + case "hrglass": // WHITE HOURGLASS + return rune(0x29d6), true + case "hscr": // MATHEMATICAL SCRIPT SMALL H + return rune(0x01d4bd), true + case "hslash": // PLANCK CONSTANT OVER TWO PI + return rune(0x210f), true + case "hstrok": // LATIN SMALL LETTER H WITH STROKE + return rune(0x0127), true + case "htimes": // VECTOR OR CROSS PRODUCT + return rune(0x2a2f), true + case "hybull": // HYPHEN BULLET + return rune(0x2043), true + case "hyphen": // HYPHEN + return rune(0x2010), true } case 'i': switch name { - case "iacgr": // GREEK SMALL LETTER IOTA WITH TONOS - return rune(0x03af), true - case "iacute": // LATIN SMALL LETTER I WITH ACUTE - return rune(0xed), true - case "ic": // INVISIBLE SEPARATOR - return rune(0x2063), true - case "icirc": // LATIN SMALL LETTER I WITH CIRCUMFLEX - return rune(0xee), true - case "icy": // CYRILLIC SMALL LETTER I - return rune(0x0438), true - case "idiagr": // GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS - return rune(0x0390), true - case "idigr": // GREEK SMALL LETTER IOTA WITH DIALYTIKA - return rune(0x03ca), true - case "iecy": // CYRILLIC SMALL LETTER IE - return rune(0x0435), true - case "iexcl": // INVERTED EXCLAMATION MARK - return rune(0xa1), true - case "iff": // LEFT RIGHT DOUBLE ARROW - return rune(0x21d4), true - case "ifr": // MATHEMATICAL FRAKTUR SMALL I - return rune(0x01d526), true - case "igr": // GREEK SMALL LETTER IOTA - return rune(0x03b9), true - case "igrave": // LATIN SMALL LETTER I WITH GRAVE - return rune(0xec), true - case "ii": // DOUBLE-STRUCK ITALIC SMALL I - return rune(0x2148), true - case "iiiint": // QUADRUPLE INTEGRAL OPERATOR - return rune(0x2a0c), true - case "iiint": // TRIPLE INTEGRAL - return rune(0x222d), true - case "iinfin": // INCOMPLETE INFINITY - return rune(0x29dc), true - case "iiota": // TURNED GREEK SMALL LETTER IOTA - return rune(0x2129), true - case "ijlig": // LATIN SMALL LIGATURE IJ - return rune(0x0133), true - case "imacr": // LATIN SMALL LETTER I WITH MACRON - return rune(0x012b), true - case "image": // BLACK-LETTER CAPITAL I - return rune(0x2111), true - case "imagline": // SCRIPT CAPITAL I - return rune(0x2110), true - case "imagpart": // BLACK-LETTER CAPITAL I - return rune(0x2111), true - case "imath": // LATIN SMALL LETTER DOTLESS I - return rune(0x0131), true - case "imof": // IMAGE OF - return rune(0x22b7), true - case "imped": // LATIN CAPITAL LETTER Z WITH STROKE - return rune(0x01b5), true - case "in": // ELEMENT OF - return rune(0x2208), true - case "incare": // CARE OF - return rune(0x2105), true - case "infin": // INFINITY - return rune(0x221e), true - case "infintie": // TIE OVER INFINITY - return rune(0x29dd), true - case "inodot": // LATIN SMALL LETTER DOTLESS I - return rune(0x0131), true - case "int": // INTEGRAL - return rune(0x222b), true - case "intcal": // INTERCALATE - return rune(0x22ba), true - case "integers": // DOUBLE-STRUCK CAPITAL Z - return rune(0x2124), true - case "intercal": // INTERCALATE - return rune(0x22ba), true - case "intlarhk": // INTEGRAL WITH LEFTWARDS ARROW WITH HOOK - return rune(0x2a17), true - case "intprod": // INTERIOR PRODUCT - return rune(0x2a3c), true - case "iocy": // CYRILLIC SMALL LETTER IO - return rune(0x0451), true - case "iogon": // LATIN SMALL LETTER I WITH OGONEK - return rune(0x012f), true - case "iopf": // MATHEMATICAL DOUBLE-STRUCK SMALL I - return rune(0x01d55a), true - case "iota": // GREEK SMALL LETTER IOTA - return rune(0x03b9), true - case "iprod": // INTERIOR PRODUCT - return rune(0x2a3c), true - case "iprodr": // RIGHTHAND INTERIOR PRODUCT - return rune(0x2a3d), true - case "iquest": // INVERTED QUESTION MARK - return rune(0xbf), true - case "iscr": // MATHEMATICAL SCRIPT SMALL I - return rune(0x01d4be), true - case "isin": // ELEMENT OF - return rune(0x2208), true - case "isinE": // ELEMENT OF WITH TWO HORIZONTAL STROKES - return rune(0x22f9), true - case "isindot": // ELEMENT OF WITH DOT ABOVE - return rune(0x22f5), true - case "isins": // SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE - return rune(0x22f4), true - case "isinsv": // ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE - return rune(0x22f3), true - case "isinv": // ELEMENT OF - return rune(0x2208), true - case "isinvb": // ELEMENT OF WITH UNDERBAR - return rune(0x22f8), true - case "it": // INVISIBLE TIMES - return rune(0x2062), true - case "itilde": // LATIN SMALL LETTER I WITH TILDE - return rune(0x0129), true - case "iukcy": // CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I - return rune(0x0456), true - case "iuml": // LATIN SMALL LETTER I WITH DIAERESIS - return rune(0xef), true + case "iacgr": // GREEK SMALL LETTER IOTA WITH TONOS + return rune(0x03af), true + case "iacute": // LATIN SMALL LETTER I WITH ACUTE + return rune(0xed), true + case "ic": // INVISIBLE SEPARATOR + return rune(0x2063), true + case "icirc": // LATIN SMALL LETTER I WITH CIRCUMFLEX + return rune(0xee), true + case "icy": // CYRILLIC SMALL LETTER I + return rune(0x0438), true + case "idiagr": // GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS + return rune(0x0390), true + case "idigr": // GREEK SMALL LETTER IOTA WITH DIALYTIKA + return rune(0x03ca), true + case "iecy": // CYRILLIC SMALL LETTER IE + return rune(0x0435), true + case "iexcl": // INVERTED EXCLAMATION MARK + return rune(0xa1), true + case "iff": // LEFT RIGHT DOUBLE ARROW + return rune(0x21d4), true + case "ifr": // MATHEMATICAL FRAKTUR SMALL I + return rune(0x01d526), true + case "igr": // GREEK SMALL LETTER IOTA + return rune(0x03b9), true + case "igrave": // LATIN SMALL LETTER I WITH GRAVE + return rune(0xec), true + case "ii": // DOUBLE-STRUCK ITALIC SMALL I + return rune(0x2148), true + case "iiiint": // QUADRUPLE INTEGRAL OPERATOR + return rune(0x2a0c), true + case "iiint": // TRIPLE INTEGRAL + return rune(0x222d), true + case "iinfin": // INCOMPLETE INFINITY + return rune(0x29dc), true + case "iiota": // TURNED GREEK SMALL LETTER IOTA + return rune(0x2129), true + case "ijlig": // LATIN SMALL LIGATURE IJ + return rune(0x0133), true + case "imacr": // LATIN SMALL LETTER I WITH MACRON + return rune(0x012b), true + case "image": // BLACK-LETTER CAPITAL I + return rune(0x2111), true + case "imagline": // SCRIPT CAPITAL I + return rune(0x2110), true + case "imagpart": // BLACK-LETTER CAPITAL I + return rune(0x2111), true + case "imath": // LATIN SMALL LETTER DOTLESS I + return rune(0x0131), true + case "imof": // IMAGE OF + return rune(0x22b7), true + case "imped": // LATIN CAPITAL LETTER Z WITH STROKE + return rune(0x01b5), true + case "in": // ELEMENT OF + return rune(0x2208), true + case "incare": // CARE OF + return rune(0x2105), true + case "infin": // INFINITY + return rune(0x221e), true + case "infintie": // TIE OVER INFINITY + return rune(0x29dd), true + case "inodot": // LATIN SMALL LETTER DOTLESS I + return rune(0x0131), true + case "int": // INTEGRAL + return rune(0x222b), true + case "intcal": // INTERCALATE + return rune(0x22ba), true + case "integers": // DOUBLE-STRUCK CAPITAL Z + return rune(0x2124), true + case "intercal": // INTERCALATE + return rune(0x22ba), true + case "intlarhk": // INTEGRAL WITH LEFTWARDS ARROW WITH HOOK + return rune(0x2a17), true + case "intprod": // INTERIOR PRODUCT + return rune(0x2a3c), true + case "iocy": // CYRILLIC SMALL LETTER IO + return rune(0x0451), true + case "iogon": // LATIN SMALL LETTER I WITH OGONEK + return rune(0x012f), true + case "iopf": // MATHEMATICAL DOUBLE-STRUCK SMALL I + return rune(0x01d55a), true + case "iota": // GREEK SMALL LETTER IOTA + return rune(0x03b9), true + case "iprod": // INTERIOR PRODUCT + return rune(0x2a3c), true + case "iprodr": // RIGHTHAND INTERIOR PRODUCT + return rune(0x2a3d), true + case "iquest": // INVERTED QUESTION MARK + return rune(0xbf), true + case "iscr": // MATHEMATICAL SCRIPT SMALL I + return rune(0x01d4be), true + case "isin": // ELEMENT OF + return rune(0x2208), true + case "isinE": // ELEMENT OF WITH TWO HORIZONTAL STROKES + return rune(0x22f9), true + case "isindot": // ELEMENT OF WITH DOT ABOVE + return rune(0x22f5), true + case "isins": // SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE + return rune(0x22f4), true + case "isinsv": // ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE + return rune(0x22f3), true + case "isinv": // ELEMENT OF + return rune(0x2208), true + case "isinvb": // ELEMENT OF WITH UNDERBAR + return rune(0x22f8), true + case "it": // INVISIBLE TIMES + return rune(0x2062), true + case "itilde": // LATIN SMALL LETTER I WITH TILDE + return rune(0x0129), true + case "iukcy": // CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I + return rune(0x0456), true + case "iuml": // LATIN SMALL LETTER I WITH DIAERESIS + return rune(0xef), true } case 'j': switch name { - case "jcirc": // LATIN SMALL LETTER J WITH CIRCUMFLEX - return rune(0x0135), true - case "jcy": // CYRILLIC SMALL LETTER SHORT I - return rune(0x0439), true - case "jfr": // MATHEMATICAL FRAKTUR SMALL J - return rune(0x01d527), true - case "jmath": // LATIN SMALL LETTER DOTLESS J - return rune(0x0237), true - case "jnodot": // LATIN SMALL LETTER DOTLESS J - return rune(0x0237), true - case "jopf": // MATHEMATICAL DOUBLE-STRUCK SMALL J - return rune(0x01d55b), true - case "jscr": // MATHEMATICAL SCRIPT SMALL J - return rune(0x01d4bf), true - case "jsercy": // CYRILLIC SMALL LETTER JE - return rune(0x0458), true - case "jukcy": // CYRILLIC SMALL LETTER UKRAINIAN IE - return rune(0x0454), true + case "jcirc": // LATIN SMALL LETTER J WITH CIRCUMFLEX + return rune(0x0135), true + case "jcy": // CYRILLIC SMALL LETTER SHORT I + return rune(0x0439), true + case "jfr": // MATHEMATICAL FRAKTUR SMALL J + return rune(0x01d527), true + case "jmath": // LATIN SMALL LETTER DOTLESS J + return rune(0x0237), true + case "jnodot": // LATIN SMALL LETTER DOTLESS J + return rune(0x0237), true + case "jopf": // MATHEMATICAL DOUBLE-STRUCK SMALL J + return rune(0x01d55b), true + case "jscr": // MATHEMATICAL SCRIPT SMALL J + return rune(0x01d4bf), true + case "jsercy": // CYRILLIC SMALL LETTER JE + return rune(0x0458), true + case "jukcy": // CYRILLIC SMALL LETTER UKRAINIAN IE + return rune(0x0454), true } case 'k': switch name { - case "kappa": // GREEK SMALL LETTER KAPPA - return rune(0x03ba), true - case "kappav": // GREEK KAPPA SYMBOL - return rune(0x03f0), true - case "kcedil": // LATIN SMALL LETTER K WITH CEDILLA - return rune(0x0137), true - case "kcy": // CYRILLIC SMALL LETTER KA - return rune(0x043a), true - case "kfr": // MATHEMATICAL FRAKTUR SMALL K - return rune(0x01d528), true - case "kgr": // GREEK SMALL LETTER KAPPA - return rune(0x03ba), true - case "kgreen": // LATIN SMALL LETTER KRA - return rune(0x0138), true - case "khcy": // CYRILLIC SMALL LETTER HA - return rune(0x0445), true - case "khgr": // GREEK SMALL LETTER CHI - return rune(0x03c7), true - case "kjcy": // CYRILLIC SMALL LETTER KJE - return rune(0x045c), true - case "kopf": // MATHEMATICAL DOUBLE-STRUCK SMALL K - return rune(0x01d55c), true - case "koppa": // GREEK LETTER KOPPA - return rune(0x03de), true - case "kscr": // MATHEMATICAL SCRIPT SMALL K - return rune(0x01d4c0), true + case "kappa": // GREEK SMALL LETTER KAPPA + return rune(0x03ba), true + case "kappav": // GREEK KAPPA SYMBOL + return rune(0x03f0), true + case "kcedil": // LATIN SMALL LETTER K WITH CEDILLA + return rune(0x0137), true + case "kcy": // CYRILLIC SMALL LETTER KA + return rune(0x043a), true + case "kfr": // MATHEMATICAL FRAKTUR SMALL K + return rune(0x01d528), true + case "kgr": // GREEK SMALL LETTER KAPPA + return rune(0x03ba), true + case "kgreen": // LATIN SMALL LETTER KRA + return rune(0x0138), true + case "khcy": // CYRILLIC SMALL LETTER HA + return rune(0x0445), true + case "khgr": // GREEK SMALL LETTER CHI + return rune(0x03c7), true + case "kjcy": // CYRILLIC SMALL LETTER KJE + return rune(0x045c), true + case "kopf": // MATHEMATICAL DOUBLE-STRUCK SMALL K + return rune(0x01d55c), true + case "koppa": // GREEK LETTER KOPPA + return rune(0x03de), true + case "kscr": // MATHEMATICAL SCRIPT SMALL K + return rune(0x01d4c0), true } case 'l': switch name { - case "lAarr": // LEFTWARDS TRIPLE ARROW - return rune(0x21da), true - case "lArr": // LEFTWARDS DOUBLE ARROW - return rune(0x21d0), true - case "lAtail": // LEFTWARDS DOUBLE ARROW-TAIL - return rune(0x291b), true - case "lBarr": // LEFTWARDS TRIPLE DASH ARROW - return rune(0x290e), true - case "lE": // LESS-THAN OVER EQUAL TO - return rune(0x2266), true - case "lEg": // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN - return rune(0x2a8b), true - case "lHar": // LEFTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB DOWN - return rune(0x2962), true - case "lacute": // LATIN SMALL LETTER L WITH ACUTE - return rune(0x013a), true - case "laemptyv": // EMPTY SET WITH LEFT ARROW ABOVE - return rune(0x29b4), true - case "lagran": // SCRIPT CAPITAL L - return rune(0x2112), true - case "lambda": // GREEK SMALL LETTER LAMDA - return rune(0x03bb), true - case "lang": // MATHEMATICAL LEFT ANGLE BRACKET - return rune(0x27e8), true - case "langd": // LEFT ANGLE BRACKET WITH DOT - return rune(0x2991), true - case "langle": // MATHEMATICAL LEFT ANGLE BRACKET - return rune(0x27e8), true - case "lap": // LESS-THAN OR APPROXIMATE - return rune(0x2a85), true - case "laquo": // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK - return rune(0xab), true - case "larr": // LEFTWARDS ARROW - return rune(0x2190), true - case "larr2": // LEFTWARDS PAIRED ARROWS - return rune(0x21c7), true - case "larrb": // LEFTWARDS ARROW TO BAR - return rune(0x21e4), true - case "larrbfs": // LEFTWARDS ARROW FROM BAR TO BLACK DIAMOND - return rune(0x291f), true - case "larrfs": // LEFTWARDS ARROW TO BLACK DIAMOND - return rune(0x291d), true - case "larrhk": // LEFTWARDS ARROW WITH HOOK - return rune(0x21a9), true - case "larrlp": // LEFTWARDS ARROW WITH LOOP - return rune(0x21ab), true - case "larrpl": // LEFT-SIDE ARC ANTICLOCKWISE ARROW - return rune(0x2939), true - case "larrsim": // LEFTWARDS ARROW ABOVE TILDE OPERATOR - return rune(0x2973), true - case "larrtl": // LEFTWARDS ARROW WITH TAIL - return rune(0x21a2), true - case "lat": // LARGER THAN - return rune(0x2aab), true - case "latail": // LEFTWARDS ARROW-TAIL - return rune(0x2919), true - case "late": // LARGER THAN OR EQUAL TO - return rune(0x2aad), true - case "lates": // LARGER THAN OR slanted EQUAL - return rune(0x2aad), true - case "lbarr": // LEFTWARDS DOUBLE DASH ARROW - return rune(0x290c), true - case "lbbrk": // LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT - return rune(0x2772), true - case "lbrace": // LEFT CURLY BRACKET - return rune(0x7b), true - case "lbrack": // LEFT SQUARE BRACKET - return rune(0x5b), true - case "lbrke": // LEFT SQUARE BRACKET WITH UNDERBAR - return rune(0x298b), true - case "lbrksld": // LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER - return rune(0x298f), true - case "lbrkslu": // LEFT SQUARE BRACKET WITH TICK IN TOP CORNER - return rune(0x298d), true - case "lcaron": // LATIN SMALL LETTER L WITH CARON - return rune(0x013e), true - case "lcedil": // LATIN SMALL LETTER L WITH CEDILLA - return rune(0x013c), true - case "lceil": // LEFT CEILING - return rune(0x2308), true - case "lcub": // LEFT CURLY BRACKET - return rune(0x7b), true - case "lcy": // CYRILLIC SMALL LETTER EL - return rune(0x043b), true - case "ldca": // ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS - return rune(0x2936), true - case "ldharb": // LEFTWARDS HARPOON WITH BARB DOWN TO BAR - return rune(0x2956), true - case "ldot": // LESS-THAN WITH DOT - return rune(0x22d6), true - case "ldquo": // LEFT DOUBLE QUOTATION MARK - return rune(0x201c), true - case "ldquor": // DOUBLE LOW-9 QUOTATION MARK - return rune(0x201e), true - case "ldrdhar": // LEFTWARDS HARPOON WITH BARB DOWN ABOVE RIGHTWARDS HARPOON WITH BARB DOWN - return rune(0x2967), true - case "ldrdshar": // LEFT BARB DOWN RIGHT BARB DOWN HARPOON - return rune(0x2950), true - case "ldrushar": // LEFT BARB DOWN RIGHT BARB UP HARPOON - return rune(0x294b), true - case "ldsh": // DOWNWARDS ARROW WITH TIP LEFTWARDS - return rune(0x21b2), true - case "le": // LESS-THAN OR EQUAL TO - return rune(0x2264), true - case "leftarrow": // LEFTWARDS ARROW - return rune(0x2190), true - case "leftarrowtail": // LEFTWARDS ARROW WITH TAIL - return rune(0x21a2), true - case "leftharpoondown": // LEFTWARDS HARPOON WITH BARB DOWNWARDS - return rune(0x21bd), true - case "leftharpoonup": // LEFTWARDS HARPOON WITH BARB UPWARDS - return rune(0x21bc), true - case "leftleftarrows": // LEFTWARDS PAIRED ARROWS - return rune(0x21c7), true - case "leftrightarrow": // LEFT RIGHT ARROW - return rune(0x2194), true - case "leftrightarrows": // LEFTWARDS ARROW OVER RIGHTWARDS ARROW - return rune(0x21c6), true - case "leftrightharpoons": // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON - return rune(0x21cb), true - case "leftrightsquigarrow": // LEFT RIGHT WAVE ARROW - return rune(0x21ad), true - case "leftthreetimes": // LEFT SEMIDIRECT PRODUCT - return rune(0x22cb), true - case "leg": // LESS-THAN EQUAL TO OR GREATER-THAN - return rune(0x22da), true - case "leq": // LESS-THAN OR EQUAL TO - return rune(0x2264), true - case "leqq": // LESS-THAN OVER EQUAL TO - return rune(0x2266), true - case "leqslant": // LESS-THAN OR SLANTED EQUAL TO - return rune(0x2a7d), true - case "les": // LESS-THAN OR SLANTED EQUAL TO - return rune(0x2a7d), true - case "lescc": // LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL - return rune(0x2aa8), true - case "lesdot": // LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE - return rune(0x2a7f), true - case "lesdoto": // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE - return rune(0x2a81), true - case "lesdotor": // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT - return rune(0x2a83), true - case "lesg": // LESS-THAN slanted EQUAL TO OR GREATER-THAN - return rune(0x22da), true - case "lesges": // LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL - return rune(0x2a93), true - case "lessapprox": // LESS-THAN OR APPROXIMATE - return rune(0x2a85), true - case "lessdot": // LESS-THAN WITH DOT - return rune(0x22d6), true - case "lesseqgtr": // LESS-THAN EQUAL TO OR GREATER-THAN - return rune(0x22da), true - case "lesseqqgtr": // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN - return rune(0x2a8b), true - case "lessgtr": // LESS-THAN OR GREATER-THAN - return rune(0x2276), true - case "lesssim": // LESS-THAN OR EQUIVALENT TO - return rune(0x2272), true - case "lfbowtie": // BOWTIE WITH LEFT HALF BLACK - return rune(0x29d1), true - case "lfisht": // LEFT FISH TAIL - return rune(0x297c), true - case "lfloor": // LEFT FLOOR - return rune(0x230a), true - case "lfr": // MATHEMATICAL FRAKTUR SMALL L - return rune(0x01d529), true - case "lftimes": // TIMES WITH LEFT HALF BLACK - return rune(0x29d4), true - case "lg": // LESS-THAN OR GREATER-THAN - return rune(0x2276), true - case "lgE": // LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL - return rune(0x2a91), true - case "lgr": // GREEK SMALL LETTER LAMDA - return rune(0x03bb), true - case "lhard": // LEFTWARDS HARPOON WITH BARB DOWNWARDS - return rune(0x21bd), true - case "lharu": // LEFTWARDS HARPOON WITH BARB UPWARDS - return rune(0x21bc), true - case "lharul": // LEFTWARDS HARPOON WITH BARB UP ABOVE LONG DASH - return rune(0x296a), true - case "lhblk": // LOWER HALF BLOCK - return rune(0x2584), true - case "ljcy": // CYRILLIC SMALL LETTER LJE - return rune(0x0459), true - case "ll": // MUCH LESS-THAN - return rune(0x226a), true - case "llarr": // LEFTWARDS PAIRED ARROWS - return rune(0x21c7), true - case "llcorner": // BOTTOM LEFT CORNER - return rune(0x231e), true - case "llhard": // LEFTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH - return rune(0x296b), true - case "lltri": // LOWER LEFT TRIANGLE - return rune(0x25fa), true - case "lltrif": // BLACK LOWER LEFT TRIANGLE - return rune(0x25e3), true - case "lmidot": // LATIN SMALL LETTER L WITH MIDDLE DOT - return rune(0x0140), true - case "lmoust": // UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION - return rune(0x23b0), true - case "lmoustache": // UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION - return rune(0x23b0), true - case "lnE": // LESS-THAN BUT NOT EQUAL TO - return rune(0x2268), true - case "lnap": // LESS-THAN AND NOT APPROXIMATE - return rune(0x2a89), true - case "lnapprox": // LESS-THAN AND NOT APPROXIMATE - return rune(0x2a89), true - case "lne": // LESS-THAN AND SINGLE-LINE NOT EQUAL TO - return rune(0x2a87), true - case "lneq": // LESS-THAN AND SINGLE-LINE NOT EQUAL TO - return rune(0x2a87), true - case "lneqq": // LESS-THAN BUT NOT EQUAL TO - return rune(0x2268), true - case "lnsim": // LESS-THAN BUT NOT EQUIVALENT TO - return rune(0x22e6), true - case "loang": // MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET - return rune(0x27ec), true - case "loarr": // LEFTWARDS OPEN-HEADED ARROW - return rune(0x21fd), true - case "lobrk": // MATHEMATICAL LEFT WHITE SQUARE BRACKET - return rune(0x27e6), true - case "locub": // LEFT WHITE CURLY BRACKET - return rune(0x2983), true - case "longleftarrow": // LONG LEFTWARDS ARROW - return rune(0x27f5), true - case "longleftrightarrow": // LONG LEFT RIGHT ARROW - return rune(0x27f7), true - case "longmapsto": // LONG RIGHTWARDS ARROW FROM BAR - return rune(0x27fc), true - case "longrightarrow": // LONG RIGHTWARDS ARROW - return rune(0x27f6), true - case "looparrowleft": // LEFTWARDS ARROW WITH LOOP - return rune(0x21ab), true - case "looparrowright": // RIGHTWARDS ARROW WITH LOOP - return rune(0x21ac), true - case "lopar": // LEFT WHITE PARENTHESIS - return rune(0x2985), true - case "lopf": // MATHEMATICAL DOUBLE-STRUCK SMALL L - return rune(0x01d55d), true - case "loplus": // PLUS SIGN IN LEFT HALF CIRCLE - return rune(0x2a2d), true - case "lotimes": // MULTIPLICATION SIGN IN LEFT HALF CIRCLE - return rune(0x2a34), true - case "lowast": // LOW ASTERISK - return rune(0x204e), true - case "lowbar": // LOW LINE - return rune(0x5f), true - case "lowint": // INTEGRAL WITH UNDERBAR - return rune(0x2a1c), true - case "loz": // LOZENGE - return rune(0x25ca), true - case "lozenge": // LOZENGE - return rune(0x25ca), true - case "lozf": // BLACK LOZENGE - return rune(0x29eb), true - case "lpar": // LEFT PARENTHESIS - return rune(0x28), true - case "lpargt": // SPHERICAL ANGLE OPENING LEFT - return rune(0x29a0), true - case "lparlt": // LEFT ARC LESS-THAN BRACKET - return rune(0x2993), true - case "lrarr": // LEFTWARDS ARROW OVER RIGHTWARDS ARROW - return rune(0x21c6), true - case "lrarr2": // LEFTWARDS ARROW OVER RIGHTWARDS ARROW - return rune(0x21c6), true - case "lrcorner": // BOTTOM RIGHT CORNER - return rune(0x231f), true - case "lrhar": // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON - return rune(0x21cb), true - case "lrhar2": // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON - return rune(0x21cb), true - case "lrhard": // RIGHTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH - return rune(0x296d), true - case "lrm": // LEFT-TO-RIGHT MARK - return rune(0x200e), true - case "lrtri": // RIGHT TRIANGLE - return rune(0x22bf), true - case "lsaquo": // SINGLE LEFT-POINTING ANGLE QUOTATION MARK - return rune(0x2039), true - case "lscr": // MATHEMATICAL SCRIPT SMALL L - return rune(0x01d4c1), true - case "lsh": // UPWARDS ARROW WITH TIP LEFTWARDS - return rune(0x21b0), true - case "lsim": // LESS-THAN OR EQUIVALENT TO - return rune(0x2272), true - case "lsime": // LESS-THAN ABOVE SIMILAR OR EQUAL - return rune(0x2a8d), true - case "lsimg": // LESS-THAN ABOVE SIMILAR ABOVE GREATER-THAN - return rune(0x2a8f), true - case "lsqb": // LEFT SQUARE BRACKET - return rune(0x5b), true - case "lsquo": // LEFT SINGLE QUOTATION MARK - return rune(0x2018), true - case "lsquor": // SINGLE LOW-9 QUOTATION MARK - return rune(0x201a), true - case "lstrok": // LATIN SMALL LETTER L WITH STROKE - return rune(0x0142), true - case "lt": // LESS-THAN SIGN - return rune(0x3c), true - case "ltcc": // LESS-THAN CLOSED BY CURVE - return rune(0x2aa6), true - case "ltcir": // LESS-THAN WITH CIRCLE INSIDE - return rune(0x2a79), true - case "ltdot": // LESS-THAN WITH DOT - return rune(0x22d6), true - case "lthree": // LEFT SEMIDIRECT PRODUCT - return rune(0x22cb), true - case "ltimes": // LEFT NORMAL FACTOR SEMIDIRECT PRODUCT - return rune(0x22c9), true - case "ltlarr": // LESS-THAN ABOVE LEFTWARDS ARROW - return rune(0x2976), true - case "ltquest": // LESS-THAN WITH QUESTION MARK ABOVE - return rune(0x2a7b), true - case "ltrPar": // DOUBLE RIGHT ARC LESS-THAN BRACKET - return rune(0x2996), true - case "ltri": // WHITE LEFT-POINTING SMALL TRIANGLE - return rune(0x25c3), true - case "ltrie": // NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22b4), true - case "ltrif": // BLACK LEFT-POINTING SMALL TRIANGLE - return rune(0x25c2), true - case "ltrivb": // LEFT TRIANGLE BESIDE VERTICAL BAR - return rune(0x29cf), true - case "luharb": // LEFTWARDS HARPOON WITH BARB UP TO BAR - return rune(0x2952), true - case "lurdshar": // LEFT BARB UP RIGHT BARB DOWN HARPOON - return rune(0x294a), true - case "luruhar": // LEFTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB UP - return rune(0x2966), true - case "lurushar": // LEFT BARB UP RIGHT BARB UP HARPOON - return rune(0x294e), true - case "lvertneqq": // LESS-THAN BUT NOT EQUAL TO - with vertical stroke - return rune(0x2268), true - case "lvnE": // LESS-THAN BUT NOT EQUAL TO - with vertical stroke - return rune(0x2268), true + case "lAarr": // LEFTWARDS TRIPLE ARROW + return rune(0x21da), true + case "lArr": // LEFTWARDS DOUBLE ARROW + return rune(0x21d0), true + case "lAtail": // LEFTWARDS DOUBLE ARROW-TAIL + return rune(0x291b), true + case "lBarr": // LEFTWARDS TRIPLE DASH ARROW + return rune(0x290e), true + case "lE": // LESS-THAN OVER EQUAL TO + return rune(0x2266), true + case "lEg": // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN + return rune(0x2a8b), true + case "lHar": // LEFTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB DOWN + return rune(0x2962), true + case "lacute": // LATIN SMALL LETTER L WITH ACUTE + return rune(0x013a), true + case "laemptyv": // EMPTY SET WITH LEFT ARROW ABOVE + return rune(0x29b4), true + case "lagran": // SCRIPT CAPITAL L + return rune(0x2112), true + case "lambda": // GREEK SMALL LETTER LAMDA + return rune(0x03bb), true + case "lang": // MATHEMATICAL LEFT ANGLE BRACKET + return rune(0x27e8), true + case "langd": // LEFT ANGLE BRACKET WITH DOT + return rune(0x2991), true + case "langle": // MATHEMATICAL LEFT ANGLE BRACKET + return rune(0x27e8), true + case "lap": // LESS-THAN OR APPROXIMATE + return rune(0x2a85), true + case "laquo": // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + return rune(0xab), true + case "larr": // LEFTWARDS ARROW + return rune(0x2190), true + case "larr2": // LEFTWARDS PAIRED ARROWS + return rune(0x21c7), true + case "larrb": // LEFTWARDS ARROW TO BAR + return rune(0x21e4), true + case "larrbfs": // LEFTWARDS ARROW FROM BAR TO BLACK DIAMOND + return rune(0x291f), true + case "larrfs": // LEFTWARDS ARROW TO BLACK DIAMOND + return rune(0x291d), true + case "larrhk": // LEFTWARDS ARROW WITH HOOK + return rune(0x21a9), true + case "larrlp": // LEFTWARDS ARROW WITH LOOP + return rune(0x21ab), true + case "larrpl": // LEFT-SIDE ARC ANTICLOCKWISE ARROW + return rune(0x2939), true + case "larrsim": // LEFTWARDS ARROW ABOVE TILDE OPERATOR + return rune(0x2973), true + case "larrtl": // LEFTWARDS ARROW WITH TAIL + return rune(0x21a2), true + case "lat": // LARGER THAN + return rune(0x2aab), true + case "latail": // LEFTWARDS ARROW-TAIL + return rune(0x2919), true + case "late": // LARGER THAN OR EQUAL TO + return rune(0x2aad), true + case "lates": // LARGER THAN OR slanted EQUAL + return rune(0x2aad), true + case "lbarr": // LEFTWARDS DOUBLE DASH ARROW + return rune(0x290c), true + case "lbbrk": // LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT + return rune(0x2772), true + case "lbrace": // LEFT CURLY BRACKET + return rune(0x7b), true + case "lbrack": // LEFT SQUARE BRACKET + return rune(0x5b), true + case "lbrke": // LEFT SQUARE BRACKET WITH UNDERBAR + return rune(0x298b), true + case "lbrksld": // LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER + return rune(0x298f), true + case "lbrkslu": // LEFT SQUARE BRACKET WITH TICK IN TOP CORNER + return rune(0x298d), true + case "lcaron": // LATIN SMALL LETTER L WITH CARON + return rune(0x013e), true + case "lcedil": // LATIN SMALL LETTER L WITH CEDILLA + return rune(0x013c), true + case "lceil": // LEFT CEILING + return rune(0x2308), true + case "lcub": // LEFT CURLY BRACKET + return rune(0x7b), true + case "lcy": // CYRILLIC SMALL LETTER EL + return rune(0x043b), true + case "ldca": // ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS + return rune(0x2936), true + case "ldharb": // LEFTWARDS HARPOON WITH BARB DOWN TO BAR + return rune(0x2956), true + case "ldot": // LESS-THAN WITH DOT + return rune(0x22d6), true + case "ldquo": // LEFT DOUBLE QUOTATION MARK + return rune(0x201c), true + case "ldquor": // DOUBLE LOW-9 QUOTATION MARK + return rune(0x201e), true + case "ldrdhar": // LEFTWARDS HARPOON WITH BARB DOWN ABOVE RIGHTWARDS HARPOON WITH BARB DOWN + return rune(0x2967), true + case "ldrdshar": // LEFT BARB DOWN RIGHT BARB DOWN HARPOON + return rune(0x2950), true + case "ldrushar": // LEFT BARB DOWN RIGHT BARB UP HARPOON + return rune(0x294b), true + case "ldsh": // DOWNWARDS ARROW WITH TIP LEFTWARDS + return rune(0x21b2), true + case "le": // LESS-THAN OR EQUAL TO + return rune(0x2264), true + case "leftarrow": // LEFTWARDS ARROW + return rune(0x2190), true + case "leftarrowtail": // LEFTWARDS ARROW WITH TAIL + return rune(0x21a2), true + case "leftharpoondown": // LEFTWARDS HARPOON WITH BARB DOWNWARDS + return rune(0x21bd), true + case "leftharpoonup": // LEFTWARDS HARPOON WITH BARB UPWARDS + return rune(0x21bc), true + case "leftleftarrows": // LEFTWARDS PAIRED ARROWS + return rune(0x21c7), true + case "leftrightarrow": // LEFT RIGHT ARROW + return rune(0x2194), true + case "leftrightarrows": // LEFTWARDS ARROW OVER RIGHTWARDS ARROW + return rune(0x21c6), true + case "leftrightharpoons": // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON + return rune(0x21cb), true + case "leftrightsquigarrow": // LEFT RIGHT WAVE ARROW + return rune(0x21ad), true + case "leftthreetimes": // LEFT SEMIDIRECT PRODUCT + return rune(0x22cb), true + case "leg": // LESS-THAN EQUAL TO OR GREATER-THAN + return rune(0x22da), true + case "leq": // LESS-THAN OR EQUAL TO + return rune(0x2264), true + case "leqq": // LESS-THAN OVER EQUAL TO + return rune(0x2266), true + case "leqslant": // LESS-THAN OR SLANTED EQUAL TO + return rune(0x2a7d), true + case "les": // LESS-THAN OR SLANTED EQUAL TO + return rune(0x2a7d), true + case "lescc": // LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL + return rune(0x2aa8), true + case "lesdot": // LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE + return rune(0x2a7f), true + case "lesdoto": // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE + return rune(0x2a81), true + case "lesdotor": // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT + return rune(0x2a83), true + case "lesg": // LESS-THAN slanted EQUAL TO OR GREATER-THAN + return rune(0x22da), true + case "lesges": // LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL + return rune(0x2a93), true + case "lessapprox": // LESS-THAN OR APPROXIMATE + return rune(0x2a85), true + case "lessdot": // LESS-THAN WITH DOT + return rune(0x22d6), true + case "lesseqgtr": // LESS-THAN EQUAL TO OR GREATER-THAN + return rune(0x22da), true + case "lesseqqgtr": // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN + return rune(0x2a8b), true + case "lessgtr": // LESS-THAN OR GREATER-THAN + return rune(0x2276), true + case "lesssim": // LESS-THAN OR EQUIVALENT TO + return rune(0x2272), true + case "lfbowtie": // BOWTIE WITH LEFT HALF BLACK + return rune(0x29d1), true + case "lfisht": // LEFT FISH TAIL + return rune(0x297c), true + case "lfloor": // LEFT FLOOR + return rune(0x230a), true + case "lfr": // MATHEMATICAL FRAKTUR SMALL L + return rune(0x01d529), true + case "lftimes": // TIMES WITH LEFT HALF BLACK + return rune(0x29d4), true + case "lg": // LESS-THAN OR GREATER-THAN + return rune(0x2276), true + case "lgE": // LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL + return rune(0x2a91), true + case "lgr": // GREEK SMALL LETTER LAMDA + return rune(0x03bb), true + case "lhard": // LEFTWARDS HARPOON WITH BARB DOWNWARDS + return rune(0x21bd), true + case "lharu": // LEFTWARDS HARPOON WITH BARB UPWARDS + return rune(0x21bc), true + case "lharul": // LEFTWARDS HARPOON WITH BARB UP ABOVE LONG DASH + return rune(0x296a), true + case "lhblk": // LOWER HALF BLOCK + return rune(0x2584), true + case "ljcy": // CYRILLIC SMALL LETTER LJE + return rune(0x0459), true + case "ll": // MUCH LESS-THAN + return rune(0x226a), true + case "llarr": // LEFTWARDS PAIRED ARROWS + return rune(0x21c7), true + case "llcorner": // BOTTOM LEFT CORNER + return rune(0x231e), true + case "llhard": // LEFTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH + return rune(0x296b), true + case "lltri": // LOWER LEFT TRIANGLE + return rune(0x25fa), true + case "lltrif": // BLACK LOWER LEFT TRIANGLE + return rune(0x25e3), true + case "lmidot": // LATIN SMALL LETTER L WITH MIDDLE DOT + return rune(0x0140), true + case "lmoust": // UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION + return rune(0x23b0), true + case "lmoustache": // UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION + return rune(0x23b0), true + case "lnE": // LESS-THAN BUT NOT EQUAL TO + return rune(0x2268), true + case "lnap": // LESS-THAN AND NOT APPROXIMATE + return rune(0x2a89), true + case "lnapprox": // LESS-THAN AND NOT APPROXIMATE + return rune(0x2a89), true + case "lne": // LESS-THAN AND SINGLE-LINE NOT EQUAL TO + return rune(0x2a87), true + case "lneq": // LESS-THAN AND SINGLE-LINE NOT EQUAL TO + return rune(0x2a87), true + case "lneqq": // LESS-THAN BUT NOT EQUAL TO + return rune(0x2268), true + case "lnsim": // LESS-THAN BUT NOT EQUIVALENT TO + return rune(0x22e6), true + case "loang": // MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET + return rune(0x27ec), true + case "loarr": // LEFTWARDS OPEN-HEADED ARROW + return rune(0x21fd), true + case "lobrk": // MATHEMATICAL LEFT WHITE SQUARE BRACKET + return rune(0x27e6), true + case "locub": // LEFT WHITE CURLY BRACKET + return rune(0x2983), true + case "longleftarrow": // LONG LEFTWARDS ARROW + return rune(0x27f5), true + case "longleftrightarrow": // LONG LEFT RIGHT ARROW + return rune(0x27f7), true + case "longmapsto": // LONG RIGHTWARDS ARROW FROM BAR + return rune(0x27fc), true + case "longrightarrow": // LONG RIGHTWARDS ARROW + return rune(0x27f6), true + case "looparrowleft": // LEFTWARDS ARROW WITH LOOP + return rune(0x21ab), true + case "looparrowright": // RIGHTWARDS ARROW WITH LOOP + return rune(0x21ac), true + case "lopar": // LEFT WHITE PARENTHESIS + return rune(0x2985), true + case "lopf": // MATHEMATICAL DOUBLE-STRUCK SMALL L + return rune(0x01d55d), true + case "loplus": // PLUS SIGN IN LEFT HALF CIRCLE + return rune(0x2a2d), true + case "lotimes": // MULTIPLICATION SIGN IN LEFT HALF CIRCLE + return rune(0x2a34), true + case "lowast": // LOW ASTERISK + return rune(0x204e), true + case "lowbar": // LOW LINE + return rune(0x5f), true + case "lowint": // INTEGRAL WITH UNDERBAR + return rune(0x2a1c), true + case "loz": // LOZENGE + return rune(0x25ca), true + case "lozenge": // LOZENGE + return rune(0x25ca), true + case "lozf": // BLACK LOZENGE + return rune(0x29eb), true + case "lpar": // LEFT PARENTHESIS + return rune(0x28), true + case "lpargt": // SPHERICAL ANGLE OPENING LEFT + return rune(0x29a0), true + case "lparlt": // LEFT ARC LESS-THAN BRACKET + return rune(0x2993), true + case "lrarr": // LEFTWARDS ARROW OVER RIGHTWARDS ARROW + return rune(0x21c6), true + case "lrarr2": // LEFTWARDS ARROW OVER RIGHTWARDS ARROW + return rune(0x21c6), true + case "lrcorner": // BOTTOM RIGHT CORNER + return rune(0x231f), true + case "lrhar": // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON + return rune(0x21cb), true + case "lrhar2": // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON + return rune(0x21cb), true + case "lrhard": // RIGHTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH + return rune(0x296d), true + case "lrm": // LEFT-TO-RIGHT MARK + return rune(0x200e), true + case "lrtri": // RIGHT TRIANGLE + return rune(0x22bf), true + case "lsaquo": // SINGLE LEFT-POINTING ANGLE QUOTATION MARK + return rune(0x2039), true + case "lscr": // MATHEMATICAL SCRIPT SMALL L + return rune(0x01d4c1), true + case "lsh": // UPWARDS ARROW WITH TIP LEFTWARDS + return rune(0x21b0), true + case "lsim": // LESS-THAN OR EQUIVALENT TO + return rune(0x2272), true + case "lsime": // LESS-THAN ABOVE SIMILAR OR EQUAL + return rune(0x2a8d), true + case "lsimg": // LESS-THAN ABOVE SIMILAR ABOVE GREATER-THAN + return rune(0x2a8f), true + case "lsqb": // LEFT SQUARE BRACKET + return rune(0x5b), true + case "lsquo": // LEFT SINGLE QUOTATION MARK + return rune(0x2018), true + case "lsquor": // SINGLE LOW-9 QUOTATION MARK + return rune(0x201a), true + case "lstrok": // LATIN SMALL LETTER L WITH STROKE + return rune(0x0142), true + case "lt": // LESS-THAN SIGN + return rune(0x3c), true + case "ltcc": // LESS-THAN CLOSED BY CURVE + return rune(0x2aa6), true + case "ltcir": // LESS-THAN WITH CIRCLE INSIDE + return rune(0x2a79), true + case "ltdot": // LESS-THAN WITH DOT + return rune(0x22d6), true + case "lthree": // LEFT SEMIDIRECT PRODUCT + return rune(0x22cb), true + case "ltimes": // LEFT NORMAL FACTOR SEMIDIRECT PRODUCT + return rune(0x22c9), true + case "ltlarr": // LESS-THAN ABOVE LEFTWARDS ARROW + return rune(0x2976), true + case "ltquest": // LESS-THAN WITH QUESTION MARK ABOVE + return rune(0x2a7b), true + case "ltrPar": // DOUBLE RIGHT ARC LESS-THAN BRACKET + return rune(0x2996), true + case "ltri": // WHITE LEFT-POINTING SMALL TRIANGLE + return rune(0x25c3), true + case "ltrie": // NORMAL SUBGROUP OF OR EQUAL TO + return rune(0x22b4), true + case "ltrif": // BLACK LEFT-POINTING SMALL TRIANGLE + return rune(0x25c2), true + case "ltrivb": // LEFT TRIANGLE BESIDE VERTICAL BAR + return rune(0x29cf), true + case "luharb": // LEFTWARDS HARPOON WITH BARB UP TO BAR + return rune(0x2952), true + case "lurdshar": // LEFT BARB UP RIGHT BARB DOWN HARPOON + return rune(0x294a), true + case "luruhar": // LEFTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB UP + return rune(0x2966), true + case "lurushar": // LEFT BARB UP RIGHT BARB UP HARPOON + return rune(0x294e), true + case "lvertneqq": // LESS-THAN BUT NOT EQUAL TO - with vertical stroke + return rune(0x2268), true + case "lvnE": // LESS-THAN BUT NOT EQUAL TO - with vertical stroke + return rune(0x2268), true } case 'm': switch name { - case "mDDot": // GEOMETRIC PROPORTION - return rune(0x223a), true - case "macr": // MACRON - return rune(0xaf), true - case "male": // MALE SIGN - return rune(0x2642), true - case "malt": // MALTESE CROSS - return rune(0x2720), true - case "maltese": // MALTESE CROSS - return rune(0x2720), true - case "map": // RIGHTWARDS ARROW FROM BAR - return rune(0x21a6), true - case "mapsto": // RIGHTWARDS ARROW FROM BAR - return rune(0x21a6), true - case "mapstodown": // DOWNWARDS ARROW FROM BAR - return rune(0x21a7), true - case "mapstoleft": // LEFTWARDS ARROW FROM BAR - return rune(0x21a4), true - case "mapstoup": // UPWARDS ARROW FROM BAR - return rune(0x21a5), true - case "marker": // BLACK VERTICAL RECTANGLE - return rune(0x25ae), true - case "mcomma": // MINUS SIGN WITH COMMA ABOVE - return rune(0x2a29), true - case "mcy": // CYRILLIC SMALL LETTER EM - return rune(0x043c), true - case "mdash": // EM DASH - return rune(0x2014), true - case "measuredangle": // MEASURED ANGLE - return rune(0x2221), true - case "mfr": // MATHEMATICAL FRAKTUR SMALL M - return rune(0x01d52a), true - case "mgr": // GREEK SMALL LETTER MU - return rune(0x03bc), true - case "mho": // INVERTED OHM SIGN - return rune(0x2127), true - case "micro": // MICRO SIGN - return rune(0xb5), true - case "mid": // DIVIDES - return rune(0x2223), true - case "midast": // ASTERISK - return rune(0x2a), true - case "midcir": // VERTICAL LINE WITH CIRCLE BELOW - return rune(0x2af0), true - case "middot": // MIDDLE DOT - return rune(0xb7), true - case "minus": // MINUS SIGN - return rune(0x2212), true - case "minusb": // SQUARED MINUS - return rune(0x229f), true - case "minusd": // DOT MINUS - return rune(0x2238), true - case "minusdu": // MINUS SIGN WITH DOT BELOW - return rune(0x2a2a), true - case "mlcp": // TRANSVERSAL INTERSECTION - return rune(0x2adb), true - case "mldr": // HORIZONTAL ELLIPSIS - return rune(0x2026), true - case "mnplus": // MINUS-OR-PLUS SIGN - return rune(0x2213), true - case "models": // MODELS - return rune(0x22a7), true - case "mopf": // MATHEMATICAL DOUBLE-STRUCK SMALL M - return rune(0x01d55e), true - case "mp": // MINUS-OR-PLUS SIGN - return rune(0x2213), true - case "mscr": // MATHEMATICAL SCRIPT SMALL M - return rune(0x01d4c2), true - case "mstpos": // INVERTED LAZY S - return rune(0x223e), true - case "mu": // GREEK SMALL LETTER MU - return rune(0x03bc), true - case "multimap": // MULTIMAP - return rune(0x22b8), true - case "mumap": // MULTIMAP - return rune(0x22b8), true + case "mDDot": // GEOMETRIC PROPORTION + return rune(0x223a), true + case "macr": // MACRON + return rune(0xaf), true + case "male": // MALE SIGN + return rune(0x2642), true + case "malt": // MALTESE CROSS + return rune(0x2720), true + case "maltese": // MALTESE CROSS + return rune(0x2720), true + case "map": // RIGHTWARDS ARROW FROM BAR + return rune(0x21a6), true + case "mapsto": // RIGHTWARDS ARROW FROM BAR + return rune(0x21a6), true + case "mapstodown": // DOWNWARDS ARROW FROM BAR + return rune(0x21a7), true + case "mapstoleft": // LEFTWARDS ARROW FROM BAR + return rune(0x21a4), true + case "mapstoup": // UPWARDS ARROW FROM BAR + return rune(0x21a5), true + case "marker": // BLACK VERTICAL RECTANGLE + return rune(0x25ae), true + case "mcomma": // MINUS SIGN WITH COMMA ABOVE + return rune(0x2a29), true + case "mcy": // CYRILLIC SMALL LETTER EM + return rune(0x043c), true + case "mdash": // EM DASH + return rune(0x2014), true + case "measuredangle": // MEASURED ANGLE + return rune(0x2221), true + case "mfr": // MATHEMATICAL FRAKTUR SMALL M + return rune(0x01d52a), true + case "mgr": // GREEK SMALL LETTER MU + return rune(0x03bc), true + case "mho": // INVERTED OHM SIGN + return rune(0x2127), true + case "micro": // MICRO SIGN + return rune(0xb5), true + case "mid": // DIVIDES + return rune(0x2223), true + case "midast": // ASTERISK + return rune(0x2a), true + case "midcir": // VERTICAL LINE WITH CIRCLE BELOW + return rune(0x2af0), true + case "middot": // MIDDLE DOT + return rune(0xb7), true + case "minus": // MINUS SIGN + return rune(0x2212), true + case "minusb": // SQUARED MINUS + return rune(0x229f), true + case "minusd": // DOT MINUS + return rune(0x2238), true + case "minusdu": // MINUS SIGN WITH DOT BELOW + return rune(0x2a2a), true + case "mlcp": // TRANSVERSAL INTERSECTION + return rune(0x2adb), true + case "mldr": // HORIZONTAL ELLIPSIS + return rune(0x2026), true + case "mnplus": // MINUS-OR-PLUS SIGN + return rune(0x2213), true + case "models": // MODELS + return rune(0x22a7), true + case "mopf": // MATHEMATICAL DOUBLE-STRUCK SMALL M + return rune(0x01d55e), true + case "mp": // MINUS-OR-PLUS SIGN + return rune(0x2213), true + case "mscr": // MATHEMATICAL SCRIPT SMALL M + return rune(0x01d4c2), true + case "mstpos": // INVERTED LAZY S + return rune(0x223e), true + case "mu": // GREEK SMALL LETTER MU + return rune(0x03bc), true + case "multimap": // MULTIMAP + return rune(0x22b8), true + case "mumap": // MULTIMAP + return rune(0x22b8), true } case 'n': switch name { - case "nGg": // VERY MUCH GREATER-THAN with slash - return rune(0x22d9), true - case "nGt": // MUCH GREATER THAN with vertical line - return rune(0x226b), true - case "nGtv": // MUCH GREATER THAN with slash - return rune(0x226b), true - case "nLeftarrow": // LEFTWARDS DOUBLE ARROW WITH STROKE - return rune(0x21cd), true - case "nLeftrightarrow": // LEFT RIGHT DOUBLE ARROW WITH STROKE - return rune(0x21ce), true - case "nLl": // VERY MUCH LESS-THAN with slash - return rune(0x22d8), true - case "nLt": // MUCH LESS THAN with vertical line - return rune(0x226a), true - case "nLtv": // MUCH LESS THAN with slash - return rune(0x226a), true - case "nRightarrow": // RIGHTWARDS DOUBLE ARROW WITH STROKE - return rune(0x21cf), true - case "nVDash": // NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE - return rune(0x22af), true - case "nVdash": // DOES NOT FORCE - return rune(0x22ae), true - case "nabla": // NABLA - return rune(0x2207), true - case "nacute": // LATIN SMALL LETTER N WITH ACUTE - return rune(0x0144), true - case "nang": // ANGLE with vertical line - return rune(0x2220), true - case "nap": // NOT ALMOST EQUAL TO - return rune(0x2249), true - case "napE": // APPROXIMATELY EQUAL OR EQUAL TO with slash - return rune(0x2a70), true - case "napid": // TRIPLE TILDE with slash - return rune(0x224b), true - case "napos": // LATIN SMALL LETTER N PRECEDED BY APOSTROPHE - return rune(0x0149), true - case "napprox": // NOT ALMOST EQUAL TO - return rune(0x2249), true - case "natur": // MUSIC NATURAL SIGN - return rune(0x266e), true - case "natural": // MUSIC NATURAL SIGN - return rune(0x266e), true - case "naturals": // DOUBLE-STRUCK CAPITAL N - return rune(0x2115), true - case "nbsp": // NO-BREAK SPACE - return rune(0xa0), true - case "nbump": // GEOMETRICALLY EQUIVALENT TO with slash - return rune(0x224e), true - case "nbumpe": // DIFFERENCE BETWEEN with slash - return rune(0x224f), true - case "ncap": // INTERSECTION WITH OVERBAR - return rune(0x2a43), true - case "ncaron": // LATIN SMALL LETTER N WITH CARON - return rune(0x0148), true - case "ncedil": // LATIN SMALL LETTER N WITH CEDILLA - return rune(0x0146), true - case "ncong": // NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO - return rune(0x2247), true - case "ncongdot": // CONGRUENT WITH DOT ABOVE with slash - return rune(0x2a6d), true - case "ncup": // UNION WITH OVERBAR - return rune(0x2a42), true - case "ncy": // CYRILLIC SMALL LETTER EN - return rune(0x043d), true - case "ndash": // EN DASH - return rune(0x2013), true - case "ne": // NOT EQUAL TO - return rune(0x2260), true - case "neArr": // NORTH EAST DOUBLE ARROW - return rune(0x21d7), true - case "nearhk": // NORTH EAST ARROW WITH HOOK - return rune(0x2924), true - case "nearr": // NORTH EAST ARROW - return rune(0x2197), true - case "nearrow": // NORTH EAST ARROW - return rune(0x2197), true - case "nedot": // APPROACHES THE LIMIT with slash - return rune(0x2250), true - case "neonwarr": // NORTH EAST ARROW CROSSING NORTH WEST ARROW - return rune(0x2931), true - case "neosearr": // NORTH EAST ARROW CROSSING SOUTH EAST ARROW - return rune(0x292e), true - case "nequiv": // NOT IDENTICAL TO - return rune(0x2262), true - case "nesear": // NORTH EAST ARROW AND SOUTH EAST ARROW - return rune(0x2928), true - case "nesim": // MINUS TILDE with slash - return rune(0x2242), true - case "neswsarr": // NORTH EAST AND SOUTH WEST ARROW - return rune(0x2922), true - case "nexist": // THERE DOES NOT EXIST - return rune(0x2204), true - case "nexists": // THERE DOES NOT EXIST - return rune(0x2204), true - case "nfr": // MATHEMATICAL FRAKTUR SMALL N - return rune(0x01d52b), true - case "ngE": // GREATER-THAN OVER EQUAL TO with slash - return rune(0x2267), true - case "nge": // NEITHER GREATER-THAN NOR EQUAL TO - return rune(0x2271), true - case "ngeq": // NEITHER GREATER-THAN NOR EQUAL TO - return rune(0x2271), true - case "ngeqq": // GREATER-THAN OVER EQUAL TO with slash - return rune(0x2267), true - case "ngeqslant": // GREATER-THAN OR SLANTED EQUAL TO with slash - return rune(0x2a7e), true - case "nges": // GREATER-THAN OR SLANTED EQUAL TO with slash - return rune(0x2a7e), true - case "ngr": // GREEK SMALL LETTER NU - return rune(0x03bd), true - case "ngsim": // NEITHER GREATER-THAN NOR EQUIVALENT TO - return rune(0x2275), true - case "ngt": // NOT GREATER-THAN - return rune(0x226f), true - case "ngtr": // NOT GREATER-THAN - return rune(0x226f), true - case "nhArr": // LEFT RIGHT DOUBLE ARROW WITH STROKE - return rune(0x21ce), true - case "nharr": // LEFT RIGHT ARROW WITH STROKE - return rune(0x21ae), true - case "nhpar": // PARALLEL WITH HORIZONTAL STROKE - return rune(0x2af2), true - case "ni": // CONTAINS AS MEMBER - return rune(0x220b), true - case "nis": // SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE - return rune(0x22fc), true - case "nisd": // CONTAINS WITH LONG HORIZONTAL STROKE - return rune(0x22fa), true - case "niv": // CONTAINS AS MEMBER - return rune(0x220b), true - case "njcy": // CYRILLIC SMALL LETTER NJE - return rune(0x045a), true - case "nlArr": // LEFTWARDS DOUBLE ARROW WITH STROKE - return rune(0x21cd), true - case "nlE": // LESS-THAN OVER EQUAL TO with slash - return rune(0x2266), true - case "nlarr": // LEFTWARDS ARROW WITH STROKE - return rune(0x219a), true - case "nldr": // TWO DOT LEADER - return rune(0x2025), true - case "nle": // NEITHER LESS-THAN NOR EQUAL TO - return rune(0x2270), true - case "nleftarrow": // LEFTWARDS ARROW WITH STROKE - return rune(0x219a), true - case "nleftrightarrow": // LEFT RIGHT ARROW WITH STROKE - return rune(0x21ae), true - case "nleq": // NEITHER LESS-THAN NOR EQUAL TO - return rune(0x2270), true - case "nleqq": // LESS-THAN OVER EQUAL TO with slash - return rune(0x2266), true - case "nleqslant": // LESS-THAN OR SLANTED EQUAL TO with slash - return rune(0x2a7d), true - case "nles": // LESS-THAN OR SLANTED EQUAL TO with slash - return rune(0x2a7d), true - case "nless": // NOT LESS-THAN - return rune(0x226e), true - case "nlsim": // NEITHER LESS-THAN NOR EQUIVALENT TO - return rune(0x2274), true - case "nlt": // NOT LESS-THAN - return rune(0x226e), true - case "nltri": // NOT NORMAL SUBGROUP OF - return rune(0x22ea), true - case "nltrie": // NOT NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22ec), true - case "nltrivb": // LEFT TRIANGLE BESIDE VERTICAL BAR with slash - return rune(0x29cf), true - case "nmid": // DOES NOT DIVIDE - return rune(0x2224), true - case "nopf": // MATHEMATICAL DOUBLE-STRUCK SMALL N - return rune(0x01d55f), true - case "not": // NOT SIGN - return rune(0xac), true - case "notin": // NOT AN ELEMENT OF - return rune(0x2209), true - case "notinE": // ELEMENT OF WITH TWO HORIZONTAL STROKES with slash - return rune(0x22f9), true - case "notindot": // ELEMENT OF WITH DOT ABOVE with slash - return rune(0x22f5), true - case "notinva": // NOT AN ELEMENT OF - return rune(0x2209), true - case "notinvb": // SMALL ELEMENT OF WITH OVERBAR - return rune(0x22f7), true - case "notinvc": // ELEMENT OF WITH OVERBAR - return rune(0x22f6), true - case "notni": // DOES NOT CONTAIN AS MEMBER - return rune(0x220c), true - case "notniva": // DOES NOT CONTAIN AS MEMBER - return rune(0x220c), true - case "notnivb": // SMALL CONTAINS WITH OVERBAR - return rune(0x22fe), true - case "notnivc": // CONTAINS WITH OVERBAR - return rune(0x22fd), true - case "npar": // NOT PARALLEL TO - return rune(0x2226), true - case "nparallel": // NOT PARALLEL TO - return rune(0x2226), true - case "nparsl": // DOUBLE SOLIDUS OPERATOR with reverse slash - return rune(0x2afd), true - case "npart": // PARTIAL DIFFERENTIAL with slash - return rune(0x2202), true - case "npolint": // LINE INTEGRATION NOT INCLUDING THE POLE - return rune(0x2a14), true - case "npr": // DOES NOT PRECEDE - return rune(0x2280), true - case "nprcue": // DOES NOT PRECEDE OR EQUAL - return rune(0x22e0), true - case "npre": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2aaf), true - case "nprec": // DOES NOT PRECEDE - return rune(0x2280), true - case "npreceq": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2aaf), true - case "nprsim": // PRECEDES OR EQUIVALENT TO with slash - return rune(0x227e), true - case "nrArr": // RIGHTWARDS DOUBLE ARROW WITH STROKE - return rune(0x21cf), true - case "nrarr": // RIGHTWARDS ARROW WITH STROKE - return rune(0x219b), true - case "nrarrc": // WAVE ARROW POINTING DIRECTLY RIGHT with slash - return rune(0x2933), true - case "nrarrw": // RIGHTWARDS WAVE ARROW with slash - return rune(0x219d), true - case "nrightarrow": // RIGHTWARDS ARROW WITH STROKE - return rune(0x219b), true - case "nrtri": // DOES NOT CONTAIN AS NORMAL SUBGROUP - return rune(0x22eb), true - case "nrtrie": // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL - return rune(0x22ed), true - case "nsGt": // DOUBLE NESTED GREATER-THAN with slash - return rune(0x2aa2), true - case "nsLt": // DOUBLE NESTED LESS-THAN with slash - return rune(0x2aa1), true - case "nsc": // DOES NOT SUCCEED - return rune(0x2281), true - case "nsccue": // DOES NOT SUCCEED OR EQUAL - return rune(0x22e1), true - case "nsce": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2ab0), true - case "nscr": // MATHEMATICAL SCRIPT SMALL N - return rune(0x01d4c3), true - case "nscsim": // SUCCEEDS OR EQUIVALENT TO with slash - return rune(0x227f), true - case "nshortmid": // DOES NOT DIVIDE - return rune(0x2224), true - case "nshortparallel": // NOT PARALLEL TO - return rune(0x2226), true - case "nsim": // NOT TILDE - return rune(0x2241), true - case "nsime": // NOT ASYMPTOTICALLY EQUAL TO - return rune(0x2244), true - case "nsimeq": // NOT ASYMPTOTICALLY EQUAL TO - return rune(0x2244), true - case "nsmid": // DOES NOT DIVIDE - return rune(0x2224), true - case "nspar": // NOT PARALLEL TO - return rune(0x2226), true - case "nsqsub": // SQUARE IMAGE OF with slash - return rune(0x228f), true - case "nsqsube": // NOT SQUARE IMAGE OF OR EQUAL TO - return rune(0x22e2), true - case "nsqsup": // SQUARE ORIGINAL OF with slash - return rune(0x2290), true - case "nsqsupe": // NOT SQUARE ORIGINAL OF OR EQUAL TO - return rune(0x22e3), true - case "nsub": // NOT A SUBSET OF - return rune(0x2284), true - case "nsubE": // SUBSET OF ABOVE EQUALS SIGN with slash - return rune(0x2ac5), true - case "nsube": // NEITHER A SUBSET OF NOR EQUAL TO - return rune(0x2288), true - case "nsubset": // SUBSET OF with vertical line - return rune(0x2282), true - case "nsubseteq": // NEITHER A SUBSET OF NOR EQUAL TO - return rune(0x2288), true - case "nsubseteqq": // SUBSET OF ABOVE EQUALS SIGN with slash - return rune(0x2ac5), true - case "nsucc": // DOES NOT SUCCEED - return rune(0x2281), true - case "nsucceq": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2ab0), true - case "nsup": // NOT A SUPERSET OF - return rune(0x2285), true - case "nsupE": // SUPERSET OF ABOVE EQUALS SIGN with slash - return rune(0x2ac6), true - case "nsupe": // NEITHER A SUPERSET OF NOR EQUAL TO - return rune(0x2289), true - case "nsupset": // SUPERSET OF with vertical line - return rune(0x2283), true - case "nsupseteq": // NEITHER A SUPERSET OF NOR EQUAL TO - return rune(0x2289), true - case "nsupseteqq": // SUPERSET OF ABOVE EQUALS SIGN with slash - return rune(0x2ac6), true - case "ntgl": // NEITHER GREATER-THAN NOR LESS-THAN - return rune(0x2279), true - case "ntilde": // LATIN SMALL LETTER N WITH TILDE - return rune(0xf1), true - case "ntlg": // NEITHER LESS-THAN NOR GREATER-THAN - return rune(0x2278), true - case "ntriangleleft": // NOT NORMAL SUBGROUP OF - return rune(0x22ea), true - case "ntrianglelefteq": // NOT NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22ec), true - case "ntriangleright": // DOES NOT CONTAIN AS NORMAL SUBGROUP - return rune(0x22eb), true - case "ntrianglerighteq": // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL - return rune(0x22ed), true - case "nu": // GREEK SMALL LETTER NU - return rune(0x03bd), true - case "num": // NUMBER SIGN - return rune(0x23), true - case "numero": // NUMERO SIGN - return rune(0x2116), true - case "numsp": // FIGURE SPACE - return rune(0x2007), true - case "nvDash": // NOT TRUE - return rune(0x22ad), true - case "nvHarr": // LEFT RIGHT DOUBLE ARROW WITH VERTICAL STROKE - return rune(0x2904), true - case "nvap": // EQUIVALENT TO with vertical line - return rune(0x224d), true - case "nvbrtri": // VERTICAL BAR BESIDE RIGHT TRIANGLE with slash - return rune(0x29d0), true - case "nvdash": // DOES NOT PROVE - return rune(0x22ac), true - case "nvge": // GREATER-THAN OR EQUAL TO with vertical line - return rune(0x2265), true - case "nvgt": // GREATER-THAN SIGN with vertical line - return rune(0x3e), true - case "nvinfin": // INFINITY NEGATED WITH VERTICAL BAR - return rune(0x29de), true - case "nvlArr": // LEFTWARDS DOUBLE ARROW WITH VERTICAL STROKE - return rune(0x2902), true - case "nvle": // LESS-THAN OR EQUAL TO with vertical line - return rune(0x2264), true - case "nvlt": // LESS-THAN SIGN with vertical line - return rune(0x3c), true - case "nvltrie": // NORMAL SUBGROUP OF OR EQUAL TO with vertical line - return rune(0x22b4), true - case "nvrArr": // RIGHTWARDS DOUBLE ARROW WITH VERTICAL STROKE - return rune(0x2903), true - case "nvrtrie": // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO with vertical line - return rune(0x22b5), true - case "nvsim": // TILDE OPERATOR with vertical line - return rune(0x223c), true - case "nwArr": // NORTH WEST DOUBLE ARROW - return rune(0x21d6), true - case "nwarhk": // NORTH WEST ARROW WITH HOOK - return rune(0x2923), true - case "nwarr": // NORTH WEST ARROW - return rune(0x2196), true - case "nwarrow": // NORTH WEST ARROW - return rune(0x2196), true - case "nwnear": // NORTH WEST ARROW AND NORTH EAST ARROW - return rune(0x2927), true - case "nwonearr": // NORTH WEST ARROW CROSSING NORTH EAST ARROW - return rune(0x2932), true - case "nwsesarr": // NORTH WEST AND SOUTH EAST ARROW - return rune(0x2921), true + case "nGg": // VERY MUCH GREATER-THAN with slash + return rune(0x22d9), true + case "nGt": // MUCH GREATER THAN with vertical line + return rune(0x226b), true + case "nGtv": // MUCH GREATER THAN with slash + return rune(0x226b), true + case "nLeftarrow": // LEFTWARDS DOUBLE ARROW WITH STROKE + return rune(0x21cd), true + case "nLeftrightarrow": // LEFT RIGHT DOUBLE ARROW WITH STROKE + return rune(0x21ce), true + case "nLl": // VERY MUCH LESS-THAN with slash + return rune(0x22d8), true + case "nLt": // MUCH LESS THAN with vertical line + return rune(0x226a), true + case "nLtv": // MUCH LESS THAN with slash + return rune(0x226a), true + case "nRightarrow": // RIGHTWARDS DOUBLE ARROW WITH STROKE + return rune(0x21cf), true + case "nVDash": // NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE + return rune(0x22af), true + case "nVdash": // DOES NOT FORCE + return rune(0x22ae), true + case "nabla": // NABLA + return rune(0x2207), true + case "nacute": // LATIN SMALL LETTER N WITH ACUTE + return rune(0x0144), true + case "nang": // ANGLE with vertical line + return rune(0x2220), true + case "nap": // NOT ALMOST EQUAL TO + return rune(0x2249), true + case "napE": // APPROXIMATELY EQUAL OR EQUAL TO with slash + return rune(0x2a70), true + case "napid": // TRIPLE TILDE with slash + return rune(0x224b), true + case "napos": // LATIN SMALL LETTER N PRECEDED BY APOSTROPHE + return rune(0x0149), true + case "napprox": // NOT ALMOST EQUAL TO + return rune(0x2249), true + case "natur": // MUSIC NATURAL SIGN + return rune(0x266e), true + case "natural": // MUSIC NATURAL SIGN + return rune(0x266e), true + case "naturals": // DOUBLE-STRUCK CAPITAL N + return rune(0x2115), true + case "nbsp": // NO-BREAK SPACE + return rune(0xa0), true + case "nbump": // GEOMETRICALLY EQUIVALENT TO with slash + return rune(0x224e), true + case "nbumpe": // DIFFERENCE BETWEEN with slash + return rune(0x224f), true + case "ncap": // INTERSECTION WITH OVERBAR + return rune(0x2a43), true + case "ncaron": // LATIN SMALL LETTER N WITH CARON + return rune(0x0148), true + case "ncedil": // LATIN SMALL LETTER N WITH CEDILLA + return rune(0x0146), true + case "ncong": // NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO + return rune(0x2247), true + case "ncongdot": // CONGRUENT WITH DOT ABOVE with slash + return rune(0x2a6d), true + case "ncup": // UNION WITH OVERBAR + return rune(0x2a42), true + case "ncy": // CYRILLIC SMALL LETTER EN + return rune(0x043d), true + case "ndash": // EN DASH + return rune(0x2013), true + case "ne": // NOT EQUAL TO + return rune(0x2260), true + case "neArr": // NORTH EAST DOUBLE ARROW + return rune(0x21d7), true + case "nearhk": // NORTH EAST ARROW WITH HOOK + return rune(0x2924), true + case "nearr": // NORTH EAST ARROW + return rune(0x2197), true + case "nearrow": // NORTH EAST ARROW + return rune(0x2197), true + case "nedot": // APPROACHES THE LIMIT with slash + return rune(0x2250), true + case "neonwarr": // NORTH EAST ARROW CROSSING NORTH WEST ARROW + return rune(0x2931), true + case "neosearr": // NORTH EAST ARROW CROSSING SOUTH EAST ARROW + return rune(0x292e), true + case "nequiv": // NOT IDENTICAL TO + return rune(0x2262), true + case "nesear": // NORTH EAST ARROW AND SOUTH EAST ARROW + return rune(0x2928), true + case "nesim": // MINUS TILDE with slash + return rune(0x2242), true + case "neswsarr": // NORTH EAST AND SOUTH WEST ARROW + return rune(0x2922), true + case "nexist": // THERE DOES NOT EXIST + return rune(0x2204), true + case "nexists": // THERE DOES NOT EXIST + return rune(0x2204), true + case "nfr": // MATHEMATICAL FRAKTUR SMALL N + return rune(0x01d52b), true + case "ngE": // GREATER-THAN OVER EQUAL TO with slash + return rune(0x2267), true + case "nge": // NEITHER GREATER-THAN NOR EQUAL TO + return rune(0x2271), true + case "ngeq": // NEITHER GREATER-THAN NOR EQUAL TO + return rune(0x2271), true + case "ngeqq": // GREATER-THAN OVER EQUAL TO with slash + return rune(0x2267), true + case "ngeqslant": // GREATER-THAN OR SLANTED EQUAL TO with slash + return rune(0x2a7e), true + case "nges": // GREATER-THAN OR SLANTED EQUAL TO with slash + return rune(0x2a7e), true + case "ngr": // GREEK SMALL LETTER NU + return rune(0x03bd), true + case "ngsim": // NEITHER GREATER-THAN NOR EQUIVALENT TO + return rune(0x2275), true + case "ngt": // NOT GREATER-THAN + return rune(0x226f), true + case "ngtr": // NOT GREATER-THAN + return rune(0x226f), true + case "nhArr": // LEFT RIGHT DOUBLE ARROW WITH STROKE + return rune(0x21ce), true + case "nharr": // LEFT RIGHT ARROW WITH STROKE + return rune(0x21ae), true + case "nhpar": // PARALLEL WITH HORIZONTAL STROKE + return rune(0x2af2), true + case "ni": // CONTAINS AS MEMBER + return rune(0x220b), true + case "nis": // SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE + return rune(0x22fc), true + case "nisd": // CONTAINS WITH LONG HORIZONTAL STROKE + return rune(0x22fa), true + case "niv": // CONTAINS AS MEMBER + return rune(0x220b), true + case "njcy": // CYRILLIC SMALL LETTER NJE + return rune(0x045a), true + case "nlArr": // LEFTWARDS DOUBLE ARROW WITH STROKE + return rune(0x21cd), true + case "nlE": // LESS-THAN OVER EQUAL TO with slash + return rune(0x2266), true + case "nlarr": // LEFTWARDS ARROW WITH STROKE + return rune(0x219a), true + case "nldr": // TWO DOT LEADER + return rune(0x2025), true + case "nle": // NEITHER LESS-THAN NOR EQUAL TO + return rune(0x2270), true + case "nleftarrow": // LEFTWARDS ARROW WITH STROKE + return rune(0x219a), true + case "nleftrightarrow": // LEFT RIGHT ARROW WITH STROKE + return rune(0x21ae), true + case "nleq": // NEITHER LESS-THAN NOR EQUAL TO + return rune(0x2270), true + case "nleqq": // LESS-THAN OVER EQUAL TO with slash + return rune(0x2266), true + case "nleqslant": // LESS-THAN OR SLANTED EQUAL TO with slash + return rune(0x2a7d), true + case "nles": // LESS-THAN OR SLANTED EQUAL TO with slash + return rune(0x2a7d), true + case "nless": // NOT LESS-THAN + return rune(0x226e), true + case "nlsim": // NEITHER LESS-THAN NOR EQUIVALENT TO + return rune(0x2274), true + case "nlt": // NOT LESS-THAN + return rune(0x226e), true + case "nltri": // NOT NORMAL SUBGROUP OF + return rune(0x22ea), true + case "nltrie": // NOT NORMAL SUBGROUP OF OR EQUAL TO + return rune(0x22ec), true + case "nltrivb": // LEFT TRIANGLE BESIDE VERTICAL BAR with slash + return rune(0x29cf), true + case "nmid": // DOES NOT DIVIDE + return rune(0x2224), true + case "nopf": // MATHEMATICAL DOUBLE-STRUCK SMALL N + return rune(0x01d55f), true + case "not": // NOT SIGN + return rune(0xac), true + case "notin": // NOT AN ELEMENT OF + return rune(0x2209), true + case "notinE": // ELEMENT OF WITH TWO HORIZONTAL STROKES with slash + return rune(0x22f9), true + case "notindot": // ELEMENT OF WITH DOT ABOVE with slash + return rune(0x22f5), true + case "notinva": // NOT AN ELEMENT OF + return rune(0x2209), true + case "notinvb": // SMALL ELEMENT OF WITH OVERBAR + return rune(0x22f7), true + case "notinvc": // ELEMENT OF WITH OVERBAR + return rune(0x22f6), true + case "notni": // DOES NOT CONTAIN AS MEMBER + return rune(0x220c), true + case "notniva": // DOES NOT CONTAIN AS MEMBER + return rune(0x220c), true + case "notnivb": // SMALL CONTAINS WITH OVERBAR + return rune(0x22fe), true + case "notnivc": // CONTAINS WITH OVERBAR + return rune(0x22fd), true + case "npar": // NOT PARALLEL TO + return rune(0x2226), true + case "nparallel": // NOT PARALLEL TO + return rune(0x2226), true + case "nparsl": // DOUBLE SOLIDUS OPERATOR with reverse slash + return rune(0x2afd), true + case "npart": // PARTIAL DIFFERENTIAL with slash + return rune(0x2202), true + case "npolint": // LINE INTEGRATION NOT INCLUDING THE POLE + return rune(0x2a14), true + case "npr": // DOES NOT PRECEDE + return rune(0x2280), true + case "nprcue": // DOES NOT PRECEDE OR EQUAL + return rune(0x22e0), true + case "npre": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash + return rune(0x2aaf), true + case "nprec": // DOES NOT PRECEDE + return rune(0x2280), true + case "npreceq": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash + return rune(0x2aaf), true + case "nprsim": // PRECEDES OR EQUIVALENT TO with slash + return rune(0x227e), true + case "nrArr": // RIGHTWARDS DOUBLE ARROW WITH STROKE + return rune(0x21cf), true + case "nrarr": // RIGHTWARDS ARROW WITH STROKE + return rune(0x219b), true + case "nrarrc": // WAVE ARROW POINTING DIRECTLY RIGHT with slash + return rune(0x2933), true + case "nrarrw": // RIGHTWARDS WAVE ARROW with slash + return rune(0x219d), true + case "nrightarrow": // RIGHTWARDS ARROW WITH STROKE + return rune(0x219b), true + case "nrtri": // DOES NOT CONTAIN AS NORMAL SUBGROUP + return rune(0x22eb), true + case "nrtrie": // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL + return rune(0x22ed), true + case "nsGt": // DOUBLE NESTED GREATER-THAN with slash + return rune(0x2aa2), true + case "nsLt": // DOUBLE NESTED LESS-THAN with slash + return rune(0x2aa1), true + case "nsc": // DOES NOT SUCCEED + return rune(0x2281), true + case "nsccue": // DOES NOT SUCCEED OR EQUAL + return rune(0x22e1), true + case "nsce": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash + return rune(0x2ab0), true + case "nscr": // MATHEMATICAL SCRIPT SMALL N + return rune(0x01d4c3), true + case "nscsim": // SUCCEEDS OR EQUIVALENT TO with slash + return rune(0x227f), true + case "nshortmid": // DOES NOT DIVIDE + return rune(0x2224), true + case "nshortparallel": // NOT PARALLEL TO + return rune(0x2226), true + case "nsim": // NOT TILDE + return rune(0x2241), true + case "nsime": // NOT ASYMPTOTICALLY EQUAL TO + return rune(0x2244), true + case "nsimeq": // NOT ASYMPTOTICALLY EQUAL TO + return rune(0x2244), true + case "nsmid": // DOES NOT DIVIDE + return rune(0x2224), true + case "nspar": // NOT PARALLEL TO + return rune(0x2226), true + case "nsqsub": // SQUARE IMAGE OF with slash + return rune(0x228f), true + case "nsqsube": // NOT SQUARE IMAGE OF OR EQUAL TO + return rune(0x22e2), true + case "nsqsup": // SQUARE ORIGINAL OF with slash + return rune(0x2290), true + case "nsqsupe": // NOT SQUARE ORIGINAL OF OR EQUAL TO + return rune(0x22e3), true + case "nsub": // NOT A SUBSET OF + return rune(0x2284), true + case "nsubE": // SUBSET OF ABOVE EQUALS SIGN with slash + return rune(0x2ac5), true + case "nsube": // NEITHER A SUBSET OF NOR EQUAL TO + return rune(0x2288), true + case "nsubset": // SUBSET OF with vertical line + return rune(0x2282), true + case "nsubseteq": // NEITHER A SUBSET OF NOR EQUAL TO + return rune(0x2288), true + case "nsubseteqq": // SUBSET OF ABOVE EQUALS SIGN with slash + return rune(0x2ac5), true + case "nsucc": // DOES NOT SUCCEED + return rune(0x2281), true + case "nsucceq": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash + return rune(0x2ab0), true + case "nsup": // NOT A SUPERSET OF + return rune(0x2285), true + case "nsupE": // SUPERSET OF ABOVE EQUALS SIGN with slash + return rune(0x2ac6), true + case "nsupe": // NEITHER A SUPERSET OF NOR EQUAL TO + return rune(0x2289), true + case "nsupset": // SUPERSET OF with vertical line + return rune(0x2283), true + case "nsupseteq": // NEITHER A SUPERSET OF NOR EQUAL TO + return rune(0x2289), true + case "nsupseteqq": // SUPERSET OF ABOVE EQUALS SIGN with slash + return rune(0x2ac6), true + case "ntgl": // NEITHER GREATER-THAN NOR LESS-THAN + return rune(0x2279), true + case "ntilde": // LATIN SMALL LETTER N WITH TILDE + return rune(0xf1), true + case "ntlg": // NEITHER LESS-THAN NOR GREATER-THAN + return rune(0x2278), true + case "ntriangleleft": // NOT NORMAL SUBGROUP OF + return rune(0x22ea), true + case "ntrianglelefteq": // NOT NORMAL SUBGROUP OF OR EQUAL TO + return rune(0x22ec), true + case "ntriangleright": // DOES NOT CONTAIN AS NORMAL SUBGROUP + return rune(0x22eb), true + case "ntrianglerighteq": // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL + return rune(0x22ed), true + case "nu": // GREEK SMALL LETTER NU + return rune(0x03bd), true + case "num": // NUMBER SIGN + return rune(0x23), true + case "numero": // NUMERO SIGN + return rune(0x2116), true + case "numsp": // FIGURE SPACE + return rune(0x2007), true + case "nvDash": // NOT TRUE + return rune(0x22ad), true + case "nvHarr": // LEFT RIGHT DOUBLE ARROW WITH VERTICAL STROKE + return rune(0x2904), true + case "nvap": // EQUIVALENT TO with vertical line + return rune(0x224d), true + case "nvbrtri": // VERTICAL BAR BESIDE RIGHT TRIANGLE with slash + return rune(0x29d0), true + case "nvdash": // DOES NOT PROVE + return rune(0x22ac), true + case "nvge": // GREATER-THAN OR EQUAL TO with vertical line + return rune(0x2265), true + case "nvgt": // GREATER-THAN SIGN with vertical line + return rune(0x3e), true + case "nvinfin": // INFINITY NEGATED WITH VERTICAL BAR + return rune(0x29de), true + case "nvlArr": // LEFTWARDS DOUBLE ARROW WITH VERTICAL STROKE + return rune(0x2902), true + case "nvle": // LESS-THAN OR EQUAL TO with vertical line + return rune(0x2264), true + case "nvlt": // LESS-THAN SIGN with vertical line + return rune(0x3c), true + case "nvltrie": // NORMAL SUBGROUP OF OR EQUAL TO with vertical line + return rune(0x22b4), true + case "nvrArr": // RIGHTWARDS DOUBLE ARROW WITH VERTICAL STROKE + return rune(0x2903), true + case "nvrtrie": // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO with vertical line + return rune(0x22b5), true + case "nvsim": // TILDE OPERATOR with vertical line + return rune(0x223c), true + case "nwArr": // NORTH WEST DOUBLE ARROW + return rune(0x21d6), true + case "nwarhk": // NORTH WEST ARROW WITH HOOK + return rune(0x2923), true + case "nwarr": // NORTH WEST ARROW + return rune(0x2196), true + case "nwarrow": // NORTH WEST ARROW + return rune(0x2196), true + case "nwnear": // NORTH WEST ARROW AND NORTH EAST ARROW + return rune(0x2927), true + case "nwonearr": // NORTH WEST ARROW CROSSING NORTH EAST ARROW + return rune(0x2932), true + case "nwsesarr": // NORTH WEST AND SOUTH EAST ARROW + return rune(0x2921), true } case 'o': switch name { - case "oS": // CIRCLED LATIN CAPITAL LETTER S - return rune(0x24c8), true - case "oacgr": // GREEK SMALL LETTER OMICRON WITH TONOS - return rune(0x03cc), true - case "oacute": // LATIN SMALL LETTER O WITH ACUTE - return rune(0xf3), true - case "oast": // CIRCLED ASTERISK OPERATOR - return rune(0x229b), true - case "obsol": // CIRCLED REVERSE SOLIDUS - return rune(0x29b8), true - case "ocir": // CIRCLED RING OPERATOR - return rune(0x229a), true - case "ocirc": // LATIN SMALL LETTER O WITH CIRCUMFLEX - return rune(0xf4), true - case "ocy": // CYRILLIC SMALL LETTER O - return rune(0x043e), true - case "odash": // CIRCLED DASH - return rune(0x229d), true - case "odblac": // LATIN SMALL LETTER O WITH DOUBLE ACUTE - return rune(0x0151), true - case "odiv": // CIRCLED DIVISION SIGN - return rune(0x2a38), true - case "odot": // CIRCLED DOT OPERATOR - return rune(0x2299), true - case "odsold": // CIRCLED ANTICLOCKWISE-ROTATED DIVISION SIGN - return rune(0x29bc), true - case "oelig": // LATIN SMALL LIGATURE OE - return rune(0x0153), true - case "ofcir": // CIRCLED BULLET - return rune(0x29bf), true - case "ofr": // MATHEMATICAL FRAKTUR SMALL O - return rune(0x01d52c), true - case "ogon": // OGONEK - return rune(0x02db), true - case "ogr": // GREEK SMALL LETTER OMICRON - return rune(0x03bf), true - case "ograve": // LATIN SMALL LETTER O WITH GRAVE - return rune(0xf2), true - case "ogt": // CIRCLED GREATER-THAN - return rune(0x29c1), true - case "ohacgr": // GREEK SMALL LETTER OMEGA WITH TONOS - return rune(0x03ce), true - case "ohbar": // CIRCLE WITH HORIZONTAL BAR - return rune(0x29b5), true - case "ohgr": // GREEK SMALL LETTER OMEGA - return rune(0x03c9), true - case "ohm": // GREEK CAPITAL LETTER OMEGA - return rune(0x03a9), true - case "oint": // CONTOUR INTEGRAL - return rune(0x222e), true - case "olarr": // ANTICLOCKWISE OPEN CIRCLE ARROW - return rune(0x21ba), true - case "olcir": // CIRCLED WHITE BULLET - return rune(0x29be), true - case "olcross": // CIRCLE WITH SUPERIMPOSED X - return rune(0x29bb), true - case "oline": // OVERLINE - return rune(0x203e), true - case "olt": // CIRCLED LESS-THAN - return rune(0x29c0), true - case "omacr": // LATIN SMALL LETTER O WITH MACRON - return rune(0x014d), true - case "omega": // GREEK SMALL LETTER OMEGA - return rune(0x03c9), true - case "omicron": // GREEK SMALL LETTER OMICRON - return rune(0x03bf), true - case "omid": // CIRCLED VERTICAL BAR - return rune(0x29b6), true - case "ominus": // CIRCLED MINUS - return rune(0x2296), true - case "oopf": // MATHEMATICAL DOUBLE-STRUCK SMALL O - return rune(0x01d560), true - case "opar": // CIRCLED PARALLEL - return rune(0x29b7), true - case "operp": // CIRCLED PERPENDICULAR - return rune(0x29b9), true - case "opfgamma": // DOUBLE-STRUCK SMALL GAMMA - return rune(0x213d), true - case "opfpi": // DOUBLE-STRUCK CAPITAL PI - return rune(0x213f), true - case "opfsum": // DOUBLE-STRUCK N-ARY SUMMATION - return rune(0x2140), true - case "oplus": // CIRCLED PLUS - return rune(0x2295), true - case "or": // LOGICAL OR - return rune(0x2228), true - case "orarr": // CLOCKWISE OPEN CIRCLE ARROW - return rune(0x21bb), true - case "ord": // LOGICAL OR WITH HORIZONTAL DASH - return rune(0x2a5d), true - case "order": // SCRIPT SMALL O - return rune(0x2134), true - case "orderof": // SCRIPT SMALL O - return rune(0x2134), true - case "ordf": // FEMININE ORDINAL INDICATOR - return rune(0xaa), true - case "ordm": // MASCULINE ORDINAL INDICATOR - return rune(0xba), true - case "origof": // ORIGINAL OF - return rune(0x22b6), true - case "oror": // TWO INTERSECTING LOGICAL OR - return rune(0x2a56), true - case "orslope": // SLOPING LARGE OR - return rune(0x2a57), true - case "orv": // LOGICAL OR WITH MIDDLE STEM - return rune(0x2a5b), true - case "oscr": // SCRIPT SMALL O - return rune(0x2134), true - case "oslash": // LATIN SMALL LETTER O WITH STROKE - return rune(0xf8), true - case "osol": // CIRCLED DIVISION SLASH - return rune(0x2298), true - case "otilde": // LATIN SMALL LETTER O WITH TILDE - return rune(0xf5), true - case "otimes": // CIRCLED TIMES - return rune(0x2297), true - case "otimesas": // CIRCLED MULTIPLICATION SIGN WITH CIRCUMFLEX ACCENT - return rune(0x2a36), true - case "ouml": // LATIN SMALL LETTER O WITH DIAERESIS - return rune(0xf6), true - case "ovbar": // APL FUNCTIONAL SYMBOL CIRCLE STILE - return rune(0x233d), true - case "ovrbrk": // TOP SQUARE BRACKET - return rune(0x23b4), true - case "ovrcub": // TOP CURLY BRACKET - return rune(0x23de), true - case "ovrpar": // TOP PARENTHESIS - return rune(0x23dc), true - case "oxuarr": // UP ARROW THROUGH CIRCLE - return rune(0x29bd), true + case "oS": // CIRCLED LATIN CAPITAL LETTER S + return rune(0x24c8), true + case "oacgr": // GREEK SMALL LETTER OMICRON WITH TONOS + return rune(0x03cc), true + case "oacute": // LATIN SMALL LETTER O WITH ACUTE + return rune(0xf3), true + case "oast": // CIRCLED ASTERISK OPERATOR + return rune(0x229b), true + case "obsol": // CIRCLED REVERSE SOLIDUS + return rune(0x29b8), true + case "ocir": // CIRCLED RING OPERATOR + return rune(0x229a), true + case "ocirc": // LATIN SMALL LETTER O WITH CIRCUMFLEX + return rune(0xf4), true + case "ocy": // CYRILLIC SMALL LETTER O + return rune(0x043e), true + case "odash": // CIRCLED DASH + return rune(0x229d), true + case "odblac": // LATIN SMALL LETTER O WITH DOUBLE ACUTE + return rune(0x0151), true + case "odiv": // CIRCLED DIVISION SIGN + return rune(0x2a38), true + case "odot": // CIRCLED DOT OPERATOR + return rune(0x2299), true + case "odsold": // CIRCLED ANTICLOCKWISE-ROTATED DIVISION SIGN + return rune(0x29bc), true + case "oelig": // LATIN SMALL LIGATURE OE + return rune(0x0153), true + case "ofcir": // CIRCLED BULLET + return rune(0x29bf), true + case "ofr": // MATHEMATICAL FRAKTUR SMALL O + return rune(0x01d52c), true + case "ogon": // OGONEK + return rune(0x02db), true + case "ogr": // GREEK SMALL LETTER OMICRON + return rune(0x03bf), true + case "ograve": // LATIN SMALL LETTER O WITH GRAVE + return rune(0xf2), true + case "ogt": // CIRCLED GREATER-THAN + return rune(0x29c1), true + case "ohacgr": // GREEK SMALL LETTER OMEGA WITH TONOS + return rune(0x03ce), true + case "ohbar": // CIRCLE WITH HORIZONTAL BAR + return rune(0x29b5), true + case "ohgr": // GREEK SMALL LETTER OMEGA + return rune(0x03c9), true + case "ohm": // GREEK CAPITAL LETTER OMEGA + return rune(0x03a9), true + case "oint": // CONTOUR INTEGRAL + return rune(0x222e), true + case "olarr": // ANTICLOCKWISE OPEN CIRCLE ARROW + return rune(0x21ba), true + case "olcir": // CIRCLED WHITE BULLET + return rune(0x29be), true + case "olcross": // CIRCLE WITH SUPERIMPOSED X + return rune(0x29bb), true + case "oline": // OVERLINE + return rune(0x203e), true + case "olt": // CIRCLED LESS-THAN + return rune(0x29c0), true + case "omacr": // LATIN SMALL LETTER O WITH MACRON + return rune(0x014d), true + case "omega": // GREEK SMALL LETTER OMEGA + return rune(0x03c9), true + case "omicron": // GREEK SMALL LETTER OMICRON + return rune(0x03bf), true + case "omid": // CIRCLED VERTICAL BAR + return rune(0x29b6), true + case "ominus": // CIRCLED MINUS + return rune(0x2296), true + case "oopf": // MATHEMATICAL DOUBLE-STRUCK SMALL O + return rune(0x01d560), true + case "opar": // CIRCLED PARALLEL + return rune(0x29b7), true + case "operp": // CIRCLED PERPENDICULAR + return rune(0x29b9), true + case "opfgamma": // DOUBLE-STRUCK SMALL GAMMA + return rune(0x213d), true + case "opfpi": // DOUBLE-STRUCK CAPITAL PI + return rune(0x213f), true + case "opfsum": // DOUBLE-STRUCK N-ARY SUMMATION + return rune(0x2140), true + case "oplus": // CIRCLED PLUS + return rune(0x2295), true + case "or": // LOGICAL OR + return rune(0x2228), true + case "orarr": // CLOCKWISE OPEN CIRCLE ARROW + return rune(0x21bb), true + case "ord": // LOGICAL OR WITH HORIZONTAL DASH + return rune(0x2a5d), true + case "order": // SCRIPT SMALL O + return rune(0x2134), true + case "orderof": // SCRIPT SMALL O + return rune(0x2134), true + case "ordf": // FEMININE ORDINAL INDICATOR + return rune(0xaa), true + case "ordm": // MASCULINE ORDINAL INDICATOR + return rune(0xba), true + case "origof": // ORIGINAL OF + return rune(0x22b6), true + case "oror": // TWO INTERSECTING LOGICAL OR + return rune(0x2a56), true + case "orslope": // SLOPING LARGE OR + return rune(0x2a57), true + case "orv": // LOGICAL OR WITH MIDDLE STEM + return rune(0x2a5b), true + case "oscr": // SCRIPT SMALL O + return rune(0x2134), true + case "oslash": // LATIN SMALL LETTER O WITH STROKE + return rune(0xf8), true + case "osol": // CIRCLED DIVISION SLASH + return rune(0x2298), true + case "otilde": // LATIN SMALL LETTER O WITH TILDE + return rune(0xf5), true + case "otimes": // CIRCLED TIMES + return rune(0x2297), true + case "otimesas": // CIRCLED MULTIPLICATION SIGN WITH CIRCUMFLEX ACCENT + return rune(0x2a36), true + case "ouml": // LATIN SMALL LETTER O WITH DIAERESIS + return rune(0xf6), true + case "ovbar": // APL FUNCTIONAL SYMBOL CIRCLE STILE + return rune(0x233d), true + case "ovrbrk": // TOP SQUARE BRACKET + return rune(0x23b4), true + case "ovrcub": // TOP CURLY BRACKET + return rune(0x23de), true + case "ovrpar": // TOP PARENTHESIS + return rune(0x23dc), true + case "oxuarr": // UP ARROW THROUGH CIRCLE + return rune(0x29bd), true } case 'p': switch name { - case "par": // PARALLEL TO - return rune(0x2225), true - case "para": // PILCROW SIGN - return rune(0xb6), true - case "parallel": // PARALLEL TO - return rune(0x2225), true - case "parsim": // PARALLEL WITH TILDE OPERATOR - return rune(0x2af3), true - case "parsl": // DOUBLE SOLIDUS OPERATOR - return rune(0x2afd), true - case "part": // PARTIAL DIFFERENTIAL - return rune(0x2202), true - case "pcy": // CYRILLIC SMALL LETTER PE - return rune(0x043f), true - case "percnt": // PERCENT SIGN - return rune(0x25), true - case "period": // FULL STOP - return rune(0x2e), true - case "permil": // PER MILLE SIGN - return rune(0x2030), true - case "perp": // UP TACK - return rune(0x22a5), true - case "pertenk": // PER TEN THOUSAND SIGN - return rune(0x2031), true - case "pfr": // MATHEMATICAL FRAKTUR SMALL P - return rune(0x01d52d), true - case "pgr": // GREEK SMALL LETTER PI - return rune(0x03c0), true - case "phgr": // GREEK SMALL LETTER PHI - return rune(0x03c6), true - case "phi": // GREEK SMALL LETTER PHI - return rune(0x03c6), true - case "phis": // GREEK PHI SYMBOL - return rune(0x03d5), true - case "phiv": // GREEK PHI SYMBOL - return rune(0x03d5), true - case "phmmat": // SCRIPT CAPITAL M - return rune(0x2133), true - case "phone": // BLACK TELEPHONE - return rune(0x260e), true - case "pi": // GREEK SMALL LETTER PI - return rune(0x03c0), true - case "pitchfork": // PITCHFORK - return rune(0x22d4), true - case "piv": // GREEK PI SYMBOL - return rune(0x03d6), true - case "planck": // PLANCK CONSTANT OVER TWO PI - return rune(0x210f), true - case "planckh": // PLANCK CONSTANT - return rune(0x210e), true - case "plankv": // PLANCK CONSTANT OVER TWO PI - return rune(0x210f), true - case "plus": // PLUS SIGN - return rune(0x2b), true - case "plusacir": // PLUS SIGN WITH CIRCUMFLEX ACCENT ABOVE - return rune(0x2a23), true - case "plusb": // SQUARED PLUS - return rune(0x229e), true - case "pluscir": // PLUS SIGN WITH SMALL CIRCLE ABOVE - return rune(0x2a22), true - case "plusdo": // DOT PLUS - return rune(0x2214), true - case "plusdu": // PLUS SIGN WITH DOT BELOW - return rune(0x2a25), true - case "pluse": // PLUS SIGN ABOVE EQUALS SIGN - return rune(0x2a72), true - case "plusmn": // PLUS-MINUS SIGN - return rune(0xb1), true - case "plussim": // PLUS SIGN WITH TILDE BELOW - return rune(0x2a26), true - case "plustrif": // PLUS SIGN WITH BLACK TRIANGLE - return rune(0x2a28), true - case "plustwo": // PLUS SIGN WITH SUBSCRIPT TWO - return rune(0x2a27), true - case "pm": // PLUS-MINUS SIGN - return rune(0xb1), true - case "pointint": // INTEGRAL AROUND A POINT OPERATOR - return rune(0x2a15), true - case "popf": // MATHEMATICAL DOUBLE-STRUCK SMALL P - return rune(0x01d561), true - case "pound": // POUND SIGN - return rune(0xa3), true - case "pr": // PRECEDES - return rune(0x227a), true - case "prE": // PRECEDES ABOVE EQUALS SIGN - return rune(0x2ab3), true - case "prap": // PRECEDES ABOVE ALMOST EQUAL TO - return rune(0x2ab7), true - case "prcue": // PRECEDES OR EQUAL TO - return rune(0x227c), true - case "pre": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2aaf), true - case "prec": // PRECEDES - return rune(0x227a), true - case "precapprox": // PRECEDES ABOVE ALMOST EQUAL TO - return rune(0x2ab7), true - case "preccurlyeq": // PRECEDES OR EQUAL TO - return rune(0x227c), true - case "preceq": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2aaf), true - case "precnapprox": // PRECEDES ABOVE NOT ALMOST EQUAL TO - return rune(0x2ab9), true - case "precneqq": // PRECEDES ABOVE NOT EQUAL TO - return rune(0x2ab5), true - case "precnsim": // PRECEDES BUT NOT EQUIVALENT TO - return rune(0x22e8), true - case "precsim": // PRECEDES OR EQUIVALENT TO - return rune(0x227e), true - case "prime": // PRIME - return rune(0x2032), true - case "primes": // DOUBLE-STRUCK CAPITAL P - return rune(0x2119), true - case "prnE": // PRECEDES ABOVE NOT EQUAL TO - return rune(0x2ab5), true - case "prnap": // PRECEDES ABOVE NOT ALMOST EQUAL TO - return rune(0x2ab9), true - case "prnsim": // PRECEDES BUT NOT EQUIVALENT TO - return rune(0x22e8), true - case "prod": // N-ARY PRODUCT - return rune(0x220f), true - case "profalar": // ALL AROUND-PROFILE - return rune(0x232e), true - case "profline": // ARC - return rune(0x2312), true - case "profsurf": // SEGMENT - return rune(0x2313), true - case "prop": // PROPORTIONAL TO - return rune(0x221d), true - case "propto": // PROPORTIONAL TO - return rune(0x221d), true - case "prsim": // PRECEDES OR EQUIVALENT TO - return rune(0x227e), true - case "prurel": // PRECEDES UNDER RELATION - return rune(0x22b0), true - case "pscr": // MATHEMATICAL SCRIPT SMALL P - return rune(0x01d4c5), true - case "psgr": // GREEK SMALL LETTER PSI - return rune(0x03c8), true - case "psi": // GREEK SMALL LETTER PSI - return rune(0x03c8), true - case "puncsp": // PUNCTUATION SPACE - return rune(0x2008), true + case "par": // PARALLEL TO + return rune(0x2225), true + case "para": // PILCROW SIGN + return rune(0xb6), true + case "parallel": // PARALLEL TO + return rune(0x2225), true + case "parsim": // PARALLEL WITH TILDE OPERATOR + return rune(0x2af3), true + case "parsl": // DOUBLE SOLIDUS OPERATOR + return rune(0x2afd), true + case "part": // PARTIAL DIFFERENTIAL + return rune(0x2202), true + case "pcy": // CYRILLIC SMALL LETTER PE + return rune(0x043f), true + case "percnt": // PERCENT SIGN + return rune(0x25), true + case "period": // FULL STOP + return rune(0x2e), true + case "permil": // PER MILLE SIGN + return rune(0x2030), true + case "perp": // UP TACK + return rune(0x22a5), true + case "pertenk": // PER TEN THOUSAND SIGN + return rune(0x2031), true + case "pfr": // MATHEMATICAL FRAKTUR SMALL P + return rune(0x01d52d), true + case "pgr": // GREEK SMALL LETTER PI + return rune(0x03c0), true + case "phgr": // GREEK SMALL LETTER PHI + return rune(0x03c6), true + case "phi": // GREEK SMALL LETTER PHI + return rune(0x03c6), true + case "phis": // GREEK PHI SYMBOL + return rune(0x03d5), true + case "phiv": // GREEK PHI SYMBOL + return rune(0x03d5), true + case "phmmat": // SCRIPT CAPITAL M + return rune(0x2133), true + case "phone": // BLACK TELEPHONE + return rune(0x260e), true + case "pi": // GREEK SMALL LETTER PI + return rune(0x03c0), true + case "pitchfork": // PITCHFORK + return rune(0x22d4), true + case "piv": // GREEK PI SYMBOL + return rune(0x03d6), true + case "planck": // PLANCK CONSTANT OVER TWO PI + return rune(0x210f), true + case "planckh": // PLANCK CONSTANT + return rune(0x210e), true + case "plankv": // PLANCK CONSTANT OVER TWO PI + return rune(0x210f), true + case "plus": // PLUS SIGN + return rune(0x2b), true + case "plusacir": // PLUS SIGN WITH CIRCUMFLEX ACCENT ABOVE + return rune(0x2a23), true + case "plusb": // SQUARED PLUS + return rune(0x229e), true + case "pluscir": // PLUS SIGN WITH SMALL CIRCLE ABOVE + return rune(0x2a22), true + case "plusdo": // DOT PLUS + return rune(0x2214), true + case "plusdu": // PLUS SIGN WITH DOT BELOW + return rune(0x2a25), true + case "pluse": // PLUS SIGN ABOVE EQUALS SIGN + return rune(0x2a72), true + case "plusmn": // PLUS-MINUS SIGN + return rune(0xb1), true + case "plussim": // PLUS SIGN WITH TILDE BELOW + return rune(0x2a26), true + case "plustrif": // PLUS SIGN WITH BLACK TRIANGLE + return rune(0x2a28), true + case "plustwo": // PLUS SIGN WITH SUBSCRIPT TWO + return rune(0x2a27), true + case "pm": // PLUS-MINUS SIGN + return rune(0xb1), true + case "pointint": // INTEGRAL AROUND A POINT OPERATOR + return rune(0x2a15), true + case "popf": // MATHEMATICAL DOUBLE-STRUCK SMALL P + return rune(0x01d561), true + case "pound": // POUND SIGN + return rune(0xa3), true + case "pr": // PRECEDES + return rune(0x227a), true + case "prE": // PRECEDES ABOVE EQUALS SIGN + return rune(0x2ab3), true + case "prap": // PRECEDES ABOVE ALMOST EQUAL TO + return rune(0x2ab7), true + case "prcue": // PRECEDES OR EQUAL TO + return rune(0x227c), true + case "pre": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN + return rune(0x2aaf), true + case "prec": // PRECEDES + return rune(0x227a), true + case "precapprox": // PRECEDES ABOVE ALMOST EQUAL TO + return rune(0x2ab7), true + case "preccurlyeq": // PRECEDES OR EQUAL TO + return rune(0x227c), true + case "preceq": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN + return rune(0x2aaf), true + case "precnapprox": // PRECEDES ABOVE NOT ALMOST EQUAL TO + return rune(0x2ab9), true + case "precneqq": // PRECEDES ABOVE NOT EQUAL TO + return rune(0x2ab5), true + case "precnsim": // PRECEDES BUT NOT EQUIVALENT TO + return rune(0x22e8), true + case "precsim": // PRECEDES OR EQUIVALENT TO + return rune(0x227e), true + case "prime": // PRIME + return rune(0x2032), true + case "primes": // DOUBLE-STRUCK CAPITAL P + return rune(0x2119), true + case "prnE": // PRECEDES ABOVE NOT EQUAL TO + return rune(0x2ab5), true + case "prnap": // PRECEDES ABOVE NOT ALMOST EQUAL TO + return rune(0x2ab9), true + case "prnsim": // PRECEDES BUT NOT EQUIVALENT TO + return rune(0x22e8), true + case "prod": // N-ARY PRODUCT + return rune(0x220f), true + case "profalar": // ALL AROUND-PROFILE + return rune(0x232e), true + case "profline": // ARC + return rune(0x2312), true + case "profsurf": // SEGMENT + return rune(0x2313), true + case "prop": // PROPORTIONAL TO + return rune(0x221d), true + case "propto": // PROPORTIONAL TO + return rune(0x221d), true + case "prsim": // PRECEDES OR EQUIVALENT TO + return rune(0x227e), true + case "prurel": // PRECEDES UNDER RELATION + return rune(0x22b0), true + case "pscr": // MATHEMATICAL SCRIPT SMALL P + return rune(0x01d4c5), true + case "psgr": // GREEK SMALL LETTER PSI + return rune(0x03c8), true + case "psi": // GREEK SMALL LETTER PSI + return rune(0x03c8), true + case "puncsp": // PUNCTUATION SPACE + return rune(0x2008), true } case 'q': switch name { - case "qfr": // MATHEMATICAL FRAKTUR SMALL Q - return rune(0x01d52e), true - case "qint": // QUADRUPLE INTEGRAL OPERATOR - return rune(0x2a0c), true - case "qopf": // MATHEMATICAL DOUBLE-STRUCK SMALL Q - return rune(0x01d562), true - case "qprime": // QUADRUPLE PRIME - return rune(0x2057), true - case "qscr": // MATHEMATICAL SCRIPT SMALL Q - return rune(0x01d4c6), true - case "quaternions": // DOUBLE-STRUCK CAPITAL H - return rune(0x210d), true - case "quatint": // QUATERNION INTEGRAL OPERATOR - return rune(0x2a16), true - case "quest": // QUESTION MARK - return rune(0x3f), true - case "questeq": // QUESTIONED EQUAL TO - return rune(0x225f), true - case "quot": // QUOTATION MARK - return rune(0x22), true + case "qfr": // MATHEMATICAL FRAKTUR SMALL Q + return rune(0x01d52e), true + case "qint": // QUADRUPLE INTEGRAL OPERATOR + return rune(0x2a0c), true + case "qopf": // MATHEMATICAL DOUBLE-STRUCK SMALL Q + return rune(0x01d562), true + case "qprime": // QUADRUPLE PRIME + return rune(0x2057), true + case "qscr": // MATHEMATICAL SCRIPT SMALL Q + return rune(0x01d4c6), true + case "quaternions": // DOUBLE-STRUCK CAPITAL H + return rune(0x210d), true + case "quatint": // QUATERNION INTEGRAL OPERATOR + return rune(0x2a16), true + case "quest": // QUESTION MARK + return rune(0x3f), true + case "questeq": // QUESTIONED EQUAL TO + return rune(0x225f), true + case "quot": // QUOTATION MARK + return rune(0x22), true } case 'r': switch name { - case "rAarr": // RIGHTWARDS TRIPLE ARROW - return rune(0x21db), true - case "rArr": // RIGHTWARDS DOUBLE ARROW - return rune(0x21d2), true - case "rAtail": // RIGHTWARDS DOUBLE ARROW-TAIL - return rune(0x291c), true - case "rBarr": // RIGHTWARDS TRIPLE DASH ARROW - return rune(0x290f), true - case "rHar": // RIGHTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB DOWN - return rune(0x2964), true - case "race": // REVERSED TILDE with underline - return rune(0x223d), true - case "racute": // LATIN SMALL LETTER R WITH ACUTE - return rune(0x0155), true - case "radic": // SQUARE ROOT - return rune(0x221a), true - case "raemptyv": // EMPTY SET WITH RIGHT ARROW ABOVE - return rune(0x29b3), true - case "rang": // MATHEMATICAL RIGHT ANGLE BRACKET - return rune(0x27e9), true - case "rangd": // RIGHT ANGLE BRACKET WITH DOT - return rune(0x2992), true - case "range": // REVERSED ANGLE WITH UNDERBAR - return rune(0x29a5), true - case "rangle": // MATHEMATICAL RIGHT ANGLE BRACKET - return rune(0x27e9), true - case "raquo": // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - return rune(0xbb), true - case "rarr": // RIGHTWARDS ARROW - return rune(0x2192), true - case "rarr2": // RIGHTWARDS PAIRED ARROWS - return rune(0x21c9), true - case "rarr3": // THREE RIGHTWARDS ARROWS - return rune(0x21f6), true - case "rarrap": // RIGHTWARDS ARROW ABOVE ALMOST EQUAL TO - return rune(0x2975), true - case "rarrb": // RIGHTWARDS ARROW TO BAR - return rune(0x21e5), true - case "rarrbfs": // RIGHTWARDS ARROW FROM BAR TO BLACK DIAMOND - return rune(0x2920), true - case "rarrc": // WAVE ARROW POINTING DIRECTLY RIGHT - return rune(0x2933), true - case "rarrfs": // RIGHTWARDS ARROW TO BLACK DIAMOND - return rune(0x291e), true - case "rarrhk": // RIGHTWARDS ARROW WITH HOOK - return rune(0x21aa), true - case "rarrlp": // RIGHTWARDS ARROW WITH LOOP - return rune(0x21ac), true - case "rarrpl": // RIGHTWARDS ARROW WITH PLUS BELOW - return rune(0x2945), true - case "rarrsim": // RIGHTWARDS ARROW ABOVE TILDE OPERATOR - return rune(0x2974), true - case "rarrtl": // RIGHTWARDS ARROW WITH TAIL - return rune(0x21a3), true - case "rarrw": // RIGHTWARDS WAVE ARROW - return rune(0x219d), true - case "rarrx": // RIGHTWARDS ARROW THROUGH X - return rune(0x2947), true - case "ratail": // RIGHTWARDS ARROW-TAIL - return rune(0x291a), true - case "ratio": // RATIO - return rune(0x2236), true - case "rationals": // DOUBLE-STRUCK CAPITAL Q - return rune(0x211a), true - case "rbarr": // RIGHTWARDS DOUBLE DASH ARROW - return rune(0x290d), true - case "rbbrk": // LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT - return rune(0x2773), true - case "rbrace": // RIGHT CURLY BRACKET - return rune(0x7d), true - case "rbrack": // RIGHT SQUARE BRACKET - return rune(0x5d), true - case "rbrke": // RIGHT SQUARE BRACKET WITH UNDERBAR - return rune(0x298c), true - case "rbrksld": // RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER - return rune(0x298e), true - case "rbrkslu": // RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER - return rune(0x2990), true - case "rcaron": // LATIN SMALL LETTER R WITH CARON - return rune(0x0159), true - case "rcedil": // LATIN SMALL LETTER R WITH CEDILLA - return rune(0x0157), true - case "rceil": // RIGHT CEILING - return rune(0x2309), true - case "rcub": // RIGHT CURLY BRACKET - return rune(0x7d), true - case "rcy": // CYRILLIC SMALL LETTER ER - return rune(0x0440), true - case "rdca": // ARROW POINTING DOWNWARDS THEN CURVING RIGHTWARDS - return rune(0x2937), true - case "rdharb": // RIGHTWARDS HARPOON WITH BARB DOWN TO BAR - return rune(0x2957), true - case "rdiag": // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT - return rune(0x2571), true - case "rdiofdi": // RISING DIAGONAL CROSSING FALLING DIAGONAL - return rune(0x292b), true - case "rdldhar": // RIGHTWARDS HARPOON WITH BARB DOWN ABOVE LEFTWARDS HARPOON WITH BARB DOWN - return rune(0x2969), true - case "rdosearr": // RISING DIAGONAL CROSSING SOUTH EAST ARROW - return rune(0x2930), true - case "rdquo": // RIGHT DOUBLE QUOTATION MARK - return rune(0x201d), true - case "rdquor": // RIGHT DOUBLE QUOTATION MARK - return rune(0x201d), true - case "rdsh": // DOWNWARDS ARROW WITH TIP RIGHTWARDS - return rune(0x21b3), true - case "real": // BLACK-LETTER CAPITAL R - return rune(0x211c), true - case "realine": // SCRIPT CAPITAL R - return rune(0x211b), true - case "realpart": // BLACK-LETTER CAPITAL R - return rune(0x211c), true - case "reals": // DOUBLE-STRUCK CAPITAL R - return rune(0x211d), true - case "rect": // WHITE RECTANGLE - return rune(0x25ad), true - case "reg": // REGISTERED SIGN - return rune(0xae), true - case "rfbowtie": // BOWTIE WITH RIGHT HALF BLACK - return rune(0x29d2), true - case "rfisht": // RIGHT FISH TAIL - return rune(0x297d), true - case "rfloor": // RIGHT FLOOR - return rune(0x230b), true - case "rfr": // MATHEMATICAL FRAKTUR SMALL R - return rune(0x01d52f), true - case "rftimes": // TIMES WITH RIGHT HALF BLACK - return rune(0x29d5), true - case "rgr": // GREEK SMALL LETTER RHO - return rune(0x03c1), true - case "rhard": // RIGHTWARDS HARPOON WITH BARB DOWNWARDS - return rune(0x21c1), true - case "rharu": // RIGHTWARDS HARPOON WITH BARB UPWARDS - return rune(0x21c0), true - case "rharul": // RIGHTWARDS HARPOON WITH BARB UP ABOVE LONG DASH - return rune(0x296c), true - case "rho": // GREEK SMALL LETTER RHO - return rune(0x03c1), true - case "rhov": // GREEK RHO SYMBOL - return rune(0x03f1), true - case "rightarrow": // RIGHTWARDS ARROW - return rune(0x2192), true - case "rightarrowtail": // RIGHTWARDS ARROW WITH TAIL - return rune(0x21a3), true - case "rightharpoondown": // RIGHTWARDS HARPOON WITH BARB DOWNWARDS - return rune(0x21c1), true - case "rightharpoonup": // RIGHTWARDS HARPOON WITH BARB UPWARDS - return rune(0x21c0), true - case "rightleftarrows": // RIGHTWARDS ARROW OVER LEFTWARDS ARROW - return rune(0x21c4), true - case "rightleftharpoons": // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON - return rune(0x21cc), true - case "rightrightarrows": // RIGHTWARDS PAIRED ARROWS - return rune(0x21c9), true - case "rightsquigarrow": // RIGHTWARDS WAVE ARROW - return rune(0x219d), true - case "rightthreetimes": // RIGHT SEMIDIRECT PRODUCT - return rune(0x22cc), true - case "rimply": // RIGHT DOUBLE ARROW WITH ROUNDED HEAD - return rune(0x2970), true - case "ring": // RING ABOVE - return rune(0x02da), true - case "risingdotseq": // IMAGE OF OR APPROXIMATELY EQUAL TO - return rune(0x2253), true - case "rlarr": // RIGHTWARDS ARROW OVER LEFTWARDS ARROW - return rune(0x21c4), true - case "rlarr2": // RIGHTWARDS ARROW OVER LEFTWARDS ARROW - return rune(0x21c4), true - case "rlhar": // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON - return rune(0x21cc), true - case "rlhar2": // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON - return rune(0x21cc), true - case "rlm": // RIGHT-TO-LEFT MARK - return rune(0x200f), true - case "rmoust": // UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION - return rune(0x23b1), true - case "rmoustache": // UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION - return rune(0x23b1), true - case "rnmid": // DOES NOT DIVIDE WITH REVERSED NEGATION SLASH - return rune(0x2aee), true - case "roang": // MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET - return rune(0x27ed), true - case "roarr": // RIGHTWARDS OPEN-HEADED ARROW - return rune(0x21fe), true - case "robrk": // MATHEMATICAL RIGHT WHITE SQUARE BRACKET - return rune(0x27e7), true - case "rocub": // RIGHT WHITE CURLY BRACKET - return rune(0x2984), true - case "ropar": // RIGHT WHITE PARENTHESIS - return rune(0x2986), true - case "ropf": // MATHEMATICAL DOUBLE-STRUCK SMALL R - return rune(0x01d563), true - case "roplus": // PLUS SIGN IN RIGHT HALF CIRCLE - return rune(0x2a2e), true - case "rotimes": // MULTIPLICATION SIGN IN RIGHT HALF CIRCLE - return rune(0x2a35), true - case "rpar": // RIGHT PARENTHESIS - return rune(0x29), true - case "rpargt": // RIGHT ARC GREATER-THAN BRACKET - return rune(0x2994), true - case "rppolint": // LINE INTEGRATION WITH RECTANGULAR PATH AROUND POLE - return rune(0x2a12), true - case "rrarr": // RIGHTWARDS PAIRED ARROWS - return rune(0x21c9), true - case "rsaquo": // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK - return rune(0x203a), true - case "rscr": // MATHEMATICAL SCRIPT SMALL R - return rune(0x01d4c7), true - case "rsh": // UPWARDS ARROW WITH TIP RIGHTWARDS - return rune(0x21b1), true - case "rsolbar": // REVERSE SOLIDUS WITH HORIZONTAL STROKE - return rune(0x29f7), true - case "rsqb": // RIGHT SQUARE BRACKET - return rune(0x5d), true - case "rsquo": // RIGHT SINGLE QUOTATION MARK - return rune(0x2019), true - case "rsquor": // RIGHT SINGLE QUOTATION MARK - return rune(0x2019), true - case "rthree": // RIGHT SEMIDIRECT PRODUCT - return rune(0x22cc), true - case "rtimes": // RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT - return rune(0x22ca), true - case "rtri": // WHITE RIGHT-POINTING SMALL TRIANGLE - return rune(0x25b9), true - case "rtrie": // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO - return rune(0x22b5), true - case "rtrif": // BLACK RIGHT-POINTING SMALL TRIANGLE - return rune(0x25b8), true - case "rtriltri": // RIGHT TRIANGLE ABOVE LEFT TRIANGLE - return rune(0x29ce), true - case "ruharb": // RIGHTWARDS HARPOON WITH BARB UP TO BAR - return rune(0x2953), true - case "ruluhar": // RIGHTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB UP - return rune(0x2968), true - case "rx": // PRESCRIPTION TAKE - return rune(0x211e), true + case "rAarr": // RIGHTWARDS TRIPLE ARROW + return rune(0x21db), true + case "rArr": // RIGHTWARDS DOUBLE ARROW + return rune(0x21d2), true + case "rAtail": // RIGHTWARDS DOUBLE ARROW-TAIL + return rune(0x291c), true + case "rBarr": // RIGHTWARDS TRIPLE DASH ARROW + return rune(0x290f), true + case "rHar": // RIGHTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB DOWN + return rune(0x2964), true + case "race": // REVERSED TILDE with underline + return rune(0x223d), true + case "racute": // LATIN SMALL LETTER R WITH ACUTE + return rune(0x0155), true + case "radic": // SQUARE ROOT + return rune(0x221a), true + case "raemptyv": // EMPTY SET WITH RIGHT ARROW ABOVE + return rune(0x29b3), true + case "rang": // MATHEMATICAL RIGHT ANGLE BRACKET + return rune(0x27e9), true + case "rangd": // RIGHT ANGLE BRACKET WITH DOT + return rune(0x2992), true + case "range": // REVERSED ANGLE WITH UNDERBAR + return rune(0x29a5), true + case "rangle": // MATHEMATICAL RIGHT ANGLE BRACKET + return rune(0x27e9), true + case "raquo": // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + return rune(0xbb), true + case "rarr": // RIGHTWARDS ARROW + return rune(0x2192), true + case "rarr2": // RIGHTWARDS PAIRED ARROWS + return rune(0x21c9), true + case "rarr3": // THREE RIGHTWARDS ARROWS + return rune(0x21f6), true + case "rarrap": // RIGHTWARDS ARROW ABOVE ALMOST EQUAL TO + return rune(0x2975), true + case "rarrb": // RIGHTWARDS ARROW TO BAR + return rune(0x21e5), true + case "rarrbfs": // RIGHTWARDS ARROW FROM BAR TO BLACK DIAMOND + return rune(0x2920), true + case "rarrc": // WAVE ARROW POINTING DIRECTLY RIGHT + return rune(0x2933), true + case "rarrfs": // RIGHTWARDS ARROW TO BLACK DIAMOND + return rune(0x291e), true + case "rarrhk": // RIGHTWARDS ARROW WITH HOOK + return rune(0x21aa), true + case "rarrlp": // RIGHTWARDS ARROW WITH LOOP + return rune(0x21ac), true + case "rarrpl": // RIGHTWARDS ARROW WITH PLUS BELOW + return rune(0x2945), true + case "rarrsim": // RIGHTWARDS ARROW ABOVE TILDE OPERATOR + return rune(0x2974), true + case "rarrtl": // RIGHTWARDS ARROW WITH TAIL + return rune(0x21a3), true + case "rarrw": // RIGHTWARDS WAVE ARROW + return rune(0x219d), true + case "rarrx": // RIGHTWARDS ARROW THROUGH X + return rune(0x2947), true + case "ratail": // RIGHTWARDS ARROW-TAIL + return rune(0x291a), true + case "ratio": // RATIO + return rune(0x2236), true + case "rationals": // DOUBLE-STRUCK CAPITAL Q + return rune(0x211a), true + case "rbarr": // RIGHTWARDS DOUBLE DASH ARROW + return rune(0x290d), true + case "rbbrk": // LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT + return rune(0x2773), true + case "rbrace": // RIGHT CURLY BRACKET + return rune(0x7d), true + case "rbrack": // RIGHT SQUARE BRACKET + return rune(0x5d), true + case "rbrke": // RIGHT SQUARE BRACKET WITH UNDERBAR + return rune(0x298c), true + case "rbrksld": // RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER + return rune(0x298e), true + case "rbrkslu": // RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER + return rune(0x2990), true + case "rcaron": // LATIN SMALL LETTER R WITH CARON + return rune(0x0159), true + case "rcedil": // LATIN SMALL LETTER R WITH CEDILLA + return rune(0x0157), true + case "rceil": // RIGHT CEILING + return rune(0x2309), true + case "rcub": // RIGHT CURLY BRACKET + return rune(0x7d), true + case "rcy": // CYRILLIC SMALL LETTER ER + return rune(0x0440), true + case "rdca": // ARROW POINTING DOWNWARDS THEN CURVING RIGHTWARDS + return rune(0x2937), true + case "rdharb": // RIGHTWARDS HARPOON WITH BARB DOWN TO BAR + return rune(0x2957), true + case "rdiag": // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT + return rune(0x2571), true + case "rdiofdi": // RISING DIAGONAL CROSSING FALLING DIAGONAL + return rune(0x292b), true + case "rdldhar": // RIGHTWARDS HARPOON WITH BARB DOWN ABOVE LEFTWARDS HARPOON WITH BARB DOWN + return rune(0x2969), true + case "rdosearr": // RISING DIAGONAL CROSSING SOUTH EAST ARROW + return rune(0x2930), true + case "rdquo": // RIGHT DOUBLE QUOTATION MARK + return rune(0x201d), true + case "rdquor": // RIGHT DOUBLE QUOTATION MARK + return rune(0x201d), true + case "rdsh": // DOWNWARDS ARROW WITH TIP RIGHTWARDS + return rune(0x21b3), true + case "real": // BLACK-LETTER CAPITAL R + return rune(0x211c), true + case "realine": // SCRIPT CAPITAL R + return rune(0x211b), true + case "realpart": // BLACK-LETTER CAPITAL R + return rune(0x211c), true + case "reals": // DOUBLE-STRUCK CAPITAL R + return rune(0x211d), true + case "rect": // WHITE RECTANGLE + return rune(0x25ad), true + case "reg": // REGISTERED SIGN + return rune(0xae), true + case "rfbowtie": // BOWTIE WITH RIGHT HALF BLACK + return rune(0x29d2), true + case "rfisht": // RIGHT FISH TAIL + return rune(0x297d), true + case "rfloor": // RIGHT FLOOR + return rune(0x230b), true + case "rfr": // MATHEMATICAL FRAKTUR SMALL R + return rune(0x01d52f), true + case "rftimes": // TIMES WITH RIGHT HALF BLACK + return rune(0x29d5), true + case "rgr": // GREEK SMALL LETTER RHO + return rune(0x03c1), true + case "rhard": // RIGHTWARDS HARPOON WITH BARB DOWNWARDS + return rune(0x21c1), true + case "rharu": // RIGHTWARDS HARPOON WITH BARB UPWARDS + return rune(0x21c0), true + case "rharul": // RIGHTWARDS HARPOON WITH BARB UP ABOVE LONG DASH + return rune(0x296c), true + case "rho": // GREEK SMALL LETTER RHO + return rune(0x03c1), true + case "rhov": // GREEK RHO SYMBOL + return rune(0x03f1), true + case "rightarrow": // RIGHTWARDS ARROW + return rune(0x2192), true + case "rightarrowtail": // RIGHTWARDS ARROW WITH TAIL + return rune(0x21a3), true + case "rightharpoondown": // RIGHTWARDS HARPOON WITH BARB DOWNWARDS + return rune(0x21c1), true + case "rightharpoonup": // RIGHTWARDS HARPOON WITH BARB UPWARDS + return rune(0x21c0), true + case "rightleftarrows": // RIGHTWARDS ARROW OVER LEFTWARDS ARROW + return rune(0x21c4), true + case "rightleftharpoons": // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON + return rune(0x21cc), true + case "rightrightarrows": // RIGHTWARDS PAIRED ARROWS + return rune(0x21c9), true + case "rightsquigarrow": // RIGHTWARDS WAVE ARROW + return rune(0x219d), true + case "rightthreetimes": // RIGHT SEMIDIRECT PRODUCT + return rune(0x22cc), true + case "rimply": // RIGHT DOUBLE ARROW WITH ROUNDED HEAD + return rune(0x2970), true + case "ring": // RING ABOVE + return rune(0x02da), true + case "risingdotseq": // IMAGE OF OR APPROXIMATELY EQUAL TO + return rune(0x2253), true + case "rlarr": // RIGHTWARDS ARROW OVER LEFTWARDS ARROW + return rune(0x21c4), true + case "rlarr2": // RIGHTWARDS ARROW OVER LEFTWARDS ARROW + return rune(0x21c4), true + case "rlhar": // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON + return rune(0x21cc), true + case "rlhar2": // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON + return rune(0x21cc), true + case "rlm": // RIGHT-TO-LEFT MARK + return rune(0x200f), true + case "rmoust": // UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION + return rune(0x23b1), true + case "rmoustache": // UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION + return rune(0x23b1), true + case "rnmid": // DOES NOT DIVIDE WITH REVERSED NEGATION SLASH + return rune(0x2aee), true + case "roang": // MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET + return rune(0x27ed), true + case "roarr": // RIGHTWARDS OPEN-HEADED ARROW + return rune(0x21fe), true + case "robrk": // MATHEMATICAL RIGHT WHITE SQUARE BRACKET + return rune(0x27e7), true + case "rocub": // RIGHT WHITE CURLY BRACKET + return rune(0x2984), true + case "ropar": // RIGHT WHITE PARENTHESIS + return rune(0x2986), true + case "ropf": // MATHEMATICAL DOUBLE-STRUCK SMALL R + return rune(0x01d563), true + case "roplus": // PLUS SIGN IN RIGHT HALF CIRCLE + return rune(0x2a2e), true + case "rotimes": // MULTIPLICATION SIGN IN RIGHT HALF CIRCLE + return rune(0x2a35), true + case "rpar": // RIGHT PARENTHESIS + return rune(0x29), true + case "rpargt": // RIGHT ARC GREATER-THAN BRACKET + return rune(0x2994), true + case "rppolint": // LINE INTEGRATION WITH RECTANGULAR PATH AROUND POLE + return rune(0x2a12), true + case "rrarr": // RIGHTWARDS PAIRED ARROWS + return rune(0x21c9), true + case "rsaquo": // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + return rune(0x203a), true + case "rscr": // MATHEMATICAL SCRIPT SMALL R + return rune(0x01d4c7), true + case "rsh": // UPWARDS ARROW WITH TIP RIGHTWARDS + return rune(0x21b1), true + case "rsolbar": // REVERSE SOLIDUS WITH HORIZONTAL STROKE + return rune(0x29f7), true + case "rsqb": // RIGHT SQUARE BRACKET + return rune(0x5d), true + case "rsquo": // RIGHT SINGLE QUOTATION MARK + return rune(0x2019), true + case "rsquor": // RIGHT SINGLE QUOTATION MARK + return rune(0x2019), true + case "rthree": // RIGHT SEMIDIRECT PRODUCT + return rune(0x22cc), true + case "rtimes": // RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT + return rune(0x22ca), true + case "rtri": // WHITE RIGHT-POINTING SMALL TRIANGLE + return rune(0x25b9), true + case "rtrie": // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO + return rune(0x22b5), true + case "rtrif": // BLACK RIGHT-POINTING SMALL TRIANGLE + return rune(0x25b8), true + case "rtriltri": // RIGHT TRIANGLE ABOVE LEFT TRIANGLE + return rune(0x29ce), true + case "ruharb": // RIGHTWARDS HARPOON WITH BARB UP TO BAR + return rune(0x2953), true + case "ruluhar": // RIGHTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB UP + return rune(0x2968), true + case "rx": // PRESCRIPTION TAKE + return rune(0x211e), true } case 's': switch name { - case "sacute": // LATIN SMALL LETTER S WITH ACUTE - return rune(0x015b), true - case "samalg": // N-ARY COPRODUCT - return rune(0x2210), true - case "sampi": // GREEK LETTER SAMPI - return rune(0x03e0), true - case "sbquo": // SINGLE LOW-9 QUOTATION MARK - return rune(0x201a), true - case "sbsol": // SMALL REVERSE SOLIDUS - return rune(0xfe68), true - case "sc": // SUCCEEDS - return rune(0x227b), true - case "scE": // SUCCEEDS ABOVE EQUALS SIGN - return rune(0x2ab4), true - case "scap": // SUCCEEDS ABOVE ALMOST EQUAL TO - return rune(0x2ab8), true - case "scaron": // LATIN SMALL LETTER S WITH CARON - return rune(0x0161), true - case "sccue": // SUCCEEDS OR EQUAL TO - return rune(0x227d), true - case "sce": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2ab0), true - case "scedil": // LATIN SMALL LETTER S WITH CEDILLA - return rune(0x015f), true - case "scirc": // LATIN SMALL LETTER S WITH CIRCUMFLEX - return rune(0x015d), true - case "scnE": // SUCCEEDS ABOVE NOT EQUAL TO - return rune(0x2ab6), true - case "scnap": // SUCCEEDS ABOVE NOT ALMOST EQUAL TO - return rune(0x2aba), true - case "scnsim": // SUCCEEDS BUT NOT EQUIVALENT TO - return rune(0x22e9), true - case "scpolint": // LINE INTEGRATION WITH SEMICIRCULAR PATH AROUND POLE - return rune(0x2a13), true - case "scsim": // SUCCEEDS OR EQUIVALENT TO - return rune(0x227f), true - case "scy": // CYRILLIC SMALL LETTER ES - return rune(0x0441), true - case "sdot": // DOT OPERATOR - return rune(0x22c5), true - case "sdotb": // SQUARED DOT OPERATOR - return rune(0x22a1), true - case "sdote": // EQUALS SIGN WITH DOT BELOW - return rune(0x2a66), true - case "seArr": // SOUTH EAST DOUBLE ARROW - return rune(0x21d8), true - case "searhk": // SOUTH EAST ARROW WITH HOOK - return rune(0x2925), true - case "searr": // SOUTH EAST ARROW - return rune(0x2198), true - case "searrow": // SOUTH EAST ARROW - return rune(0x2198), true - case "sect": // SECTION SIGN - return rune(0xa7), true - case "semi": // SEMICOLON - return rune(0x3b), true - case "seonearr": // SOUTH EAST ARROW CROSSING NORTH EAST ARROW - return rune(0x292d), true - case "seswar": // SOUTH EAST ARROW AND SOUTH WEST ARROW - return rune(0x2929), true - case "setminus": // SET MINUS - return rune(0x2216), true - case "setmn": // SET MINUS - return rune(0x2216), true - case "sext": // SIX POINTED BLACK STAR - return rune(0x2736), true - case "sfgr": // GREEK SMALL LETTER FINAL SIGMA - return rune(0x03c2), true - case "sfr": // MATHEMATICAL FRAKTUR SMALL S - return rune(0x01d530), true - case "sfrown": // FROWN - return rune(0x2322), true - case "sgr": // GREEK SMALL LETTER SIGMA - return rune(0x03c3), true - case "sharp": // MUSIC SHARP SIGN - return rune(0x266f), true - case "shchcy": // CYRILLIC SMALL LETTER SHCHA - return rune(0x0449), true - case "shcy": // CYRILLIC SMALL LETTER SHA - return rune(0x0448), true - case "shortmid": // DIVIDES - return rune(0x2223), true - case "shortparallel": // PARALLEL TO - return rune(0x2225), true - case "shuffle": // SHUFFLE PRODUCT - return rune(0x29e2), true - case "shy": // SOFT HYPHEN - return rune(0xad), true - case "sigma": // GREEK SMALL LETTER SIGMA - return rune(0x03c3), true - case "sigmaf": // GREEK SMALL LETTER FINAL SIGMA - return rune(0x03c2), true - case "sigmav": // GREEK SMALL LETTER FINAL SIGMA - return rune(0x03c2), true - case "sim": // TILDE OPERATOR - return rune(0x223c), true - case "simdot": // TILDE OPERATOR WITH DOT ABOVE - return rune(0x2a6a), true - case "sime": // ASYMPTOTICALLY EQUAL TO - return rune(0x2243), true - case "simeq": // ASYMPTOTICALLY EQUAL TO - return rune(0x2243), true - case "simg": // SIMILAR OR GREATER-THAN - return rune(0x2a9e), true - case "simgE": // SIMILAR ABOVE GREATER-THAN ABOVE EQUALS SIGN - return rune(0x2aa0), true - case "siml": // SIMILAR OR LESS-THAN - return rune(0x2a9d), true - case "simlE": // SIMILAR ABOVE LESS-THAN ABOVE EQUALS SIGN - return rune(0x2a9f), true - case "simne": // APPROXIMATELY BUT NOT ACTUALLY EQUAL TO - return rune(0x2246), true - case "simplus": // PLUS SIGN WITH TILDE ABOVE - return rune(0x2a24), true - case "simrarr": // TILDE OPERATOR ABOVE RIGHTWARDS ARROW - return rune(0x2972), true - case "slarr": // LEFTWARDS ARROW - return rune(0x2190), true - case "slint": // INTEGRAL AVERAGE WITH SLASH - return rune(0x2a0f), true - case "smallsetminus": // SET MINUS - return rune(0x2216), true - case "smashp": // SMASH PRODUCT - return rune(0x2a33), true - case "smeparsl": // EQUALS SIGN AND SLANTED PARALLEL WITH TILDE ABOVE - return rune(0x29e4), true - case "smid": // DIVIDES - return rune(0x2223), true - case "smile": // SMILE - return rune(0x2323), true - case "smt": // SMALLER THAN - return rune(0x2aaa), true - case "smte": // SMALLER THAN OR EQUAL TO - return rune(0x2aac), true - case "smtes": // SMALLER THAN OR slanted EQUAL - return rune(0x2aac), true - case "softcy": // CYRILLIC SMALL LETTER SOFT SIGN - return rune(0x044c), true - case "sol": // SOLIDUS - return rune(0x2f), true - case "solb": // SQUARED RISING DIAGONAL SLASH - return rune(0x29c4), true - case "solbar": // APL FUNCTIONAL SYMBOL SLASH BAR - return rune(0x233f), true - case "sopf": // MATHEMATICAL DOUBLE-STRUCK SMALL S - return rune(0x01d564), true - case "spades": // BLACK SPADE SUIT - return rune(0x2660), true - case "spadesuit": // BLACK SPADE SUIT - return rune(0x2660), true - case "spar": // PARALLEL TO - return rune(0x2225), true - case "sqcap": // SQUARE CAP - return rune(0x2293), true - case "sqcaps": // SQUARE CAP with serifs - return rune(0x2293), true - case "sqcup": // SQUARE CUP - return rune(0x2294), true - case "sqcups": // SQUARE CUP with serifs - return rune(0x2294), true - case "sqsub": // SQUARE IMAGE OF - return rune(0x228f), true - case "sqsube": // SQUARE IMAGE OF OR EQUAL TO - return rune(0x2291), true - case "sqsubset": // SQUARE IMAGE OF - return rune(0x228f), true - case "sqsubseteq": // SQUARE IMAGE OF OR EQUAL TO - return rune(0x2291), true - case "sqsup": // SQUARE ORIGINAL OF - return rune(0x2290), true - case "sqsupe": // SQUARE ORIGINAL OF OR EQUAL TO - return rune(0x2292), true - case "sqsupset": // SQUARE ORIGINAL OF - return rune(0x2290), true - case "sqsupseteq": // SQUARE ORIGINAL OF OR EQUAL TO - return rune(0x2292), true - case "squ": // WHITE SQUARE - return rune(0x25a1), true - case "square": // WHITE SQUARE - return rune(0x25a1), true - case "squarf": // BLACK SMALL SQUARE - return rune(0x25aa), true - case "squb": // SQUARED SQUARE - return rune(0x29c8), true - case "squerr": // ERROR-BARRED WHITE SQUARE - return rune(0x29ee), true - case "squf": // BLACK SMALL SQUARE - return rune(0x25aa), true - case "squferr": // ERROR-BARRED BLACK SQUARE - return rune(0x29ef), true - case "srarr": // RIGHTWARDS ARROW - return rune(0x2192), true - case "sscr": // MATHEMATICAL SCRIPT SMALL S - return rune(0x01d4c8), true - case "ssetmn": // SET MINUS - return rune(0x2216), true - case "ssmile": // SMILE - return rune(0x2323), true - case "sstarf": // STAR OPERATOR - return rune(0x22c6), true - case "star": // WHITE STAR - return rune(0x2606), true - case "starf": // BLACK STAR - return rune(0x2605), true - case "stigma": // GREEK LETTER STIGMA - return rune(0x03da), true - case "straightepsilon": // GREEK LUNATE EPSILON SYMBOL - return rune(0x03f5), true - case "straightphi": // GREEK PHI SYMBOL - return rune(0x03d5), true - case "strns": // MACRON - return rune(0xaf), true - case "sub": // SUBSET OF - return rune(0x2282), true - case "subE": // SUBSET OF ABOVE EQUALS SIGN - return rune(0x2ac5), true - case "subdot": // SUBSET WITH DOT - return rune(0x2abd), true - case "sube": // SUBSET OF OR EQUAL TO - return rune(0x2286), true - case "subedot": // SUBSET OF OR EQUAL TO WITH DOT ABOVE - return rune(0x2ac3), true - case "submult": // SUBSET WITH MULTIPLICATION SIGN BELOW - return rune(0x2ac1), true - case "subnE": // SUBSET OF ABOVE NOT EQUAL TO - return rune(0x2acb), true - case "subne": // SUBSET OF WITH NOT EQUAL TO - return rune(0x228a), true - case "subplus": // SUBSET WITH PLUS SIGN BELOW - return rune(0x2abf), true - case "subrarr": // SUBSET ABOVE RIGHTWARDS ARROW - return rune(0x2979), true - case "subset": // SUBSET OF - return rune(0x2282), true - case "subseteq": // SUBSET OF OR EQUAL TO - return rune(0x2286), true - case "subseteqq": // SUBSET OF ABOVE EQUALS SIGN - return rune(0x2ac5), true - case "subsetneq": // SUBSET OF WITH NOT EQUAL TO - return rune(0x228a), true - case "subsetneqq": // SUBSET OF ABOVE NOT EQUAL TO - return rune(0x2acb), true - case "subsim": // SUBSET OF ABOVE TILDE OPERATOR - return rune(0x2ac7), true - case "subsub": // SUBSET ABOVE SUBSET - return rune(0x2ad5), true - case "subsup": // SUBSET ABOVE SUPERSET - return rune(0x2ad3), true - case "succ": // SUCCEEDS - return rune(0x227b), true - case "succapprox": // SUCCEEDS ABOVE ALMOST EQUAL TO - return rune(0x2ab8), true - case "succcurlyeq": // SUCCEEDS OR EQUAL TO - return rune(0x227d), true - case "succeq": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2ab0), true - case "succnapprox": // SUCCEEDS ABOVE NOT ALMOST EQUAL TO - return rune(0x2aba), true - case "succneqq": // SUCCEEDS ABOVE NOT EQUAL TO - return rune(0x2ab6), true - case "succnsim": // SUCCEEDS BUT NOT EQUIVALENT TO - return rune(0x22e9), true - case "succsim": // SUCCEEDS OR EQUIVALENT TO - return rune(0x227f), true - case "sum": // N-ARY SUMMATION - return rune(0x2211), true - case "sumint": // SUMMATION WITH INTEGRAL - return rune(0x2a0b), true - case "sung": // EIGHTH NOTE - return rune(0x266a), true - case "sup": // SUPERSET OF - return rune(0x2283), true - case "sup1": // SUPERSCRIPT ONE - return rune(0xb9), true - case "sup2": // SUPERSCRIPT TWO - return rune(0xb2), true - case "sup3": // SUPERSCRIPT THREE - return rune(0xb3), true - case "supE": // SUPERSET OF ABOVE EQUALS SIGN - return rune(0x2ac6), true - case "supdot": // SUPERSET WITH DOT - return rune(0x2abe), true - case "supdsub": // SUPERSET BESIDE AND JOINED BY DASH WITH SUBSET - return rune(0x2ad8), true - case "supe": // SUPERSET OF OR EQUAL TO - return rune(0x2287), true - case "supedot": // SUPERSET OF OR EQUAL TO WITH DOT ABOVE - return rune(0x2ac4), true - case "suphsol": // SUPERSET PRECEDING SOLIDUS - return rune(0x27c9), true - case "suphsub": // SUPERSET BESIDE SUBSET - return rune(0x2ad7), true - case "suplarr": // SUPERSET ABOVE LEFTWARDS ARROW - return rune(0x297b), true - case "supmult": // SUPERSET WITH MULTIPLICATION SIGN BELOW - return rune(0x2ac2), true - case "supnE": // SUPERSET OF ABOVE NOT EQUAL TO - return rune(0x2acc), true - case "supne": // SUPERSET OF WITH NOT EQUAL TO - return rune(0x228b), true - case "supplus": // SUPERSET WITH PLUS SIGN BELOW - return rune(0x2ac0), true - case "supset": // SUPERSET OF - return rune(0x2283), true - case "supseteq": // SUPERSET OF OR EQUAL TO - return rune(0x2287), true - case "supseteqq": // SUPERSET OF ABOVE EQUALS SIGN - return rune(0x2ac6), true - case "supsetneq": // SUPERSET OF WITH NOT EQUAL TO - return rune(0x228b), true - case "supsetneqq": // SUPERSET OF ABOVE NOT EQUAL TO - return rune(0x2acc), true - case "supsim": // SUPERSET OF ABOVE TILDE OPERATOR - return rune(0x2ac8), true - case "supsub": // SUPERSET ABOVE SUBSET - return rune(0x2ad4), true - case "supsup": // SUPERSET ABOVE SUPERSET - return rune(0x2ad6), true - case "swArr": // SOUTH WEST DOUBLE ARROW - return rune(0x21d9), true - case "swarhk": // SOUTH WEST ARROW WITH HOOK - return rune(0x2926), true - case "swarr": // SOUTH WEST ARROW - return rune(0x2199), true - case "swarrow": // SOUTH WEST ARROW - return rune(0x2199), true - case "swnwar": // SOUTH WEST ARROW AND NORTH WEST ARROW - return rune(0x292a), true - case "szlig": // LATIN SMALL LETTER SHARP S - return rune(0xdf), true + case "sacute": // LATIN SMALL LETTER S WITH ACUTE + return rune(0x015b), true + case "samalg": // N-ARY COPRODUCT + return rune(0x2210), true + case "sampi": // GREEK LETTER SAMPI + return rune(0x03e0), true + case "sbquo": // SINGLE LOW-9 QUOTATION MARK + return rune(0x201a), true + case "sbsol": // SMALL REVERSE SOLIDUS + return rune(0xfe68), true + case "sc": // SUCCEEDS + return rune(0x227b), true + case "scE": // SUCCEEDS ABOVE EQUALS SIGN + return rune(0x2ab4), true + case "scap": // SUCCEEDS ABOVE ALMOST EQUAL TO + return rune(0x2ab8), true + case "scaron": // LATIN SMALL LETTER S WITH CARON + return rune(0x0161), true + case "sccue": // SUCCEEDS OR EQUAL TO + return rune(0x227d), true + case "sce": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN + return rune(0x2ab0), true + case "scedil": // LATIN SMALL LETTER S WITH CEDILLA + return rune(0x015f), true + case "scirc": // LATIN SMALL LETTER S WITH CIRCUMFLEX + return rune(0x015d), true + case "scnE": // SUCCEEDS ABOVE NOT EQUAL TO + return rune(0x2ab6), true + case "scnap": // SUCCEEDS ABOVE NOT ALMOST EQUAL TO + return rune(0x2aba), true + case "scnsim": // SUCCEEDS BUT NOT EQUIVALENT TO + return rune(0x22e9), true + case "scpolint": // LINE INTEGRATION WITH SEMICIRCULAR PATH AROUND POLE + return rune(0x2a13), true + case "scsim": // SUCCEEDS OR EQUIVALENT TO + return rune(0x227f), true + case "scy": // CYRILLIC SMALL LETTER ES + return rune(0x0441), true + case "sdot": // DOT OPERATOR + return rune(0x22c5), true + case "sdotb": // SQUARED DOT OPERATOR + return rune(0x22a1), true + case "sdote": // EQUALS SIGN WITH DOT BELOW + return rune(0x2a66), true + case "seArr": // SOUTH EAST DOUBLE ARROW + return rune(0x21d8), true + case "searhk": // SOUTH EAST ARROW WITH HOOK + return rune(0x2925), true + case "searr": // SOUTH EAST ARROW + return rune(0x2198), true + case "searrow": // SOUTH EAST ARROW + return rune(0x2198), true + case "sect": // SECTION SIGN + return rune(0xa7), true + case "semi": // SEMICOLON + return rune(0x3b), true + case "seonearr": // SOUTH EAST ARROW CROSSING NORTH EAST ARROW + return rune(0x292d), true + case "seswar": // SOUTH EAST ARROW AND SOUTH WEST ARROW + return rune(0x2929), true + case "setminus": // SET MINUS + return rune(0x2216), true + case "setmn": // SET MINUS + return rune(0x2216), true + case "sext": // SIX POINTED BLACK STAR + return rune(0x2736), true + case "sfgr": // GREEK SMALL LETTER FINAL SIGMA + return rune(0x03c2), true + case "sfr": // MATHEMATICAL FRAKTUR SMALL S + return rune(0x01d530), true + case "sfrown": // FROWN + return rune(0x2322), true + case "sgr": // GREEK SMALL LETTER SIGMA + return rune(0x03c3), true + case "sharp": // MUSIC SHARP SIGN + return rune(0x266f), true + case "shchcy": // CYRILLIC SMALL LETTER SHCHA + return rune(0x0449), true + case "shcy": // CYRILLIC SMALL LETTER SHA + return rune(0x0448), true + case "shortmid": // DIVIDES + return rune(0x2223), true + case "shortparallel": // PARALLEL TO + return rune(0x2225), true + case "shuffle": // SHUFFLE PRODUCT + return rune(0x29e2), true + case "shy": // SOFT HYPHEN + return rune(0xad), true + case "sigma": // GREEK SMALL LETTER SIGMA + return rune(0x03c3), true + case "sigmaf": // GREEK SMALL LETTER FINAL SIGMA + return rune(0x03c2), true + case "sigmav": // GREEK SMALL LETTER FINAL SIGMA + return rune(0x03c2), true + case "sim": // TILDE OPERATOR + return rune(0x223c), true + case "simdot": // TILDE OPERATOR WITH DOT ABOVE + return rune(0x2a6a), true + case "sime": // ASYMPTOTICALLY EQUAL TO + return rune(0x2243), true + case "simeq": // ASYMPTOTICALLY EQUAL TO + return rune(0x2243), true + case "simg": // SIMILAR OR GREATER-THAN + return rune(0x2a9e), true + case "simgE": // SIMILAR ABOVE GREATER-THAN ABOVE EQUALS SIGN + return rune(0x2aa0), true + case "siml": // SIMILAR OR LESS-THAN + return rune(0x2a9d), true + case "simlE": // SIMILAR ABOVE LESS-THAN ABOVE EQUALS SIGN + return rune(0x2a9f), true + case "simne": // APPROXIMATELY BUT NOT ACTUALLY EQUAL TO + return rune(0x2246), true + case "simplus": // PLUS SIGN WITH TILDE ABOVE + return rune(0x2a24), true + case "simrarr": // TILDE OPERATOR ABOVE RIGHTWARDS ARROW + return rune(0x2972), true + case "slarr": // LEFTWARDS ARROW + return rune(0x2190), true + case "slint": // INTEGRAL AVERAGE WITH SLASH + return rune(0x2a0f), true + case "smallsetminus": // SET MINUS + return rune(0x2216), true + case "smashp": // SMASH PRODUCT + return rune(0x2a33), true + case "smeparsl": // EQUALS SIGN AND SLANTED PARALLEL WITH TILDE ABOVE + return rune(0x29e4), true + case "smid": // DIVIDES + return rune(0x2223), true + case "smile": // SMILE + return rune(0x2323), true + case "smt": // SMALLER THAN + return rune(0x2aaa), true + case "smte": // SMALLER THAN OR EQUAL TO + return rune(0x2aac), true + case "smtes": // SMALLER THAN OR slanted EQUAL + return rune(0x2aac), true + case "softcy": // CYRILLIC SMALL LETTER SOFT SIGN + return rune(0x044c), true + case "sol": // SOLIDUS + return rune(0x2f), true + case "solb": // SQUARED RISING DIAGONAL SLASH + return rune(0x29c4), true + case "solbar": // APL FUNCTIONAL SYMBOL SLASH BAR + return rune(0x233f), true + case "sopf": // MATHEMATICAL DOUBLE-STRUCK SMALL S + return rune(0x01d564), true + case "spades": // BLACK SPADE SUIT + return rune(0x2660), true + case "spadesuit": // BLACK SPADE SUIT + return rune(0x2660), true + case "spar": // PARALLEL TO + return rune(0x2225), true + case "sqcap": // SQUARE CAP + return rune(0x2293), true + case "sqcaps": // SQUARE CAP with serifs + return rune(0x2293), true + case "sqcup": // SQUARE CUP + return rune(0x2294), true + case "sqcups": // SQUARE CUP with serifs + return rune(0x2294), true + case "sqsub": // SQUARE IMAGE OF + return rune(0x228f), true + case "sqsube": // SQUARE IMAGE OF OR EQUAL TO + return rune(0x2291), true + case "sqsubset": // SQUARE IMAGE OF + return rune(0x228f), true + case "sqsubseteq": // SQUARE IMAGE OF OR EQUAL TO + return rune(0x2291), true + case "sqsup": // SQUARE ORIGINAL OF + return rune(0x2290), true + case "sqsupe": // SQUARE ORIGINAL OF OR EQUAL TO + return rune(0x2292), true + case "sqsupset": // SQUARE ORIGINAL OF + return rune(0x2290), true + case "sqsupseteq": // SQUARE ORIGINAL OF OR EQUAL TO + return rune(0x2292), true + case "squ": // WHITE SQUARE + return rune(0x25a1), true + case "square": // WHITE SQUARE + return rune(0x25a1), true + case "squarf": // BLACK SMALL SQUARE + return rune(0x25aa), true + case "squb": // SQUARED SQUARE + return rune(0x29c8), true + case "squerr": // ERROR-BARRED WHITE SQUARE + return rune(0x29ee), true + case "squf": // BLACK SMALL SQUARE + return rune(0x25aa), true + case "squferr": // ERROR-BARRED BLACK SQUARE + return rune(0x29ef), true + case "srarr": // RIGHTWARDS ARROW + return rune(0x2192), true + case "sscr": // MATHEMATICAL SCRIPT SMALL S + return rune(0x01d4c8), true + case "ssetmn": // SET MINUS + return rune(0x2216), true + case "ssmile": // SMILE + return rune(0x2323), true + case "sstarf": // STAR OPERATOR + return rune(0x22c6), true + case "star": // WHITE STAR + return rune(0x2606), true + case "starf": // BLACK STAR + return rune(0x2605), true + case "stigma": // GREEK LETTER STIGMA + return rune(0x03da), true + case "straightepsilon": // GREEK LUNATE EPSILON SYMBOL + return rune(0x03f5), true + case "straightphi": // GREEK PHI SYMBOL + return rune(0x03d5), true + case "strns": // MACRON + return rune(0xaf), true + case "sub": // SUBSET OF + return rune(0x2282), true + case "subE": // SUBSET OF ABOVE EQUALS SIGN + return rune(0x2ac5), true + case "subdot": // SUBSET WITH DOT + return rune(0x2abd), true + case "sube": // SUBSET OF OR EQUAL TO + return rune(0x2286), true + case "subedot": // SUBSET OF OR EQUAL TO WITH DOT ABOVE + return rune(0x2ac3), true + case "submult": // SUBSET WITH MULTIPLICATION SIGN BELOW + return rune(0x2ac1), true + case "subnE": // SUBSET OF ABOVE NOT EQUAL TO + return rune(0x2acb), true + case "subne": // SUBSET OF WITH NOT EQUAL TO + return rune(0x228a), true + case "subplus": // SUBSET WITH PLUS SIGN BELOW + return rune(0x2abf), true + case "subrarr": // SUBSET ABOVE RIGHTWARDS ARROW + return rune(0x2979), true + case "subset": // SUBSET OF + return rune(0x2282), true + case "subseteq": // SUBSET OF OR EQUAL TO + return rune(0x2286), true + case "subseteqq": // SUBSET OF ABOVE EQUALS SIGN + return rune(0x2ac5), true + case "subsetneq": // SUBSET OF WITH NOT EQUAL TO + return rune(0x228a), true + case "subsetneqq": // SUBSET OF ABOVE NOT EQUAL TO + return rune(0x2acb), true + case "subsim": // SUBSET OF ABOVE TILDE OPERATOR + return rune(0x2ac7), true + case "subsub": // SUBSET ABOVE SUBSET + return rune(0x2ad5), true + case "subsup": // SUBSET ABOVE SUPERSET + return rune(0x2ad3), true + case "succ": // SUCCEEDS + return rune(0x227b), true + case "succapprox": // SUCCEEDS ABOVE ALMOST EQUAL TO + return rune(0x2ab8), true + case "succcurlyeq": // SUCCEEDS OR EQUAL TO + return rune(0x227d), true + case "succeq": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN + return rune(0x2ab0), true + case "succnapprox": // SUCCEEDS ABOVE NOT ALMOST EQUAL TO + return rune(0x2aba), true + case "succneqq": // SUCCEEDS ABOVE NOT EQUAL TO + return rune(0x2ab6), true + case "succnsim": // SUCCEEDS BUT NOT EQUIVALENT TO + return rune(0x22e9), true + case "succsim": // SUCCEEDS OR EQUIVALENT TO + return rune(0x227f), true + case "sum": // N-ARY SUMMATION + return rune(0x2211), true + case "sumint": // SUMMATION WITH INTEGRAL + return rune(0x2a0b), true + case "sung": // EIGHTH NOTE + return rune(0x266a), true + case "sup": // SUPERSET OF + return rune(0x2283), true + case "sup1": // SUPERSCRIPT ONE + return rune(0xb9), true + case "sup2": // SUPERSCRIPT TWO + return rune(0xb2), true + case "sup3": // SUPERSCRIPT THREE + return rune(0xb3), true + case "supE": // SUPERSET OF ABOVE EQUALS SIGN + return rune(0x2ac6), true + case "supdot": // SUPERSET WITH DOT + return rune(0x2abe), true + case "supdsub": // SUPERSET BESIDE AND JOINED BY DASH WITH SUBSET + return rune(0x2ad8), true + case "supe": // SUPERSET OF OR EQUAL TO + return rune(0x2287), true + case "supedot": // SUPERSET OF OR EQUAL TO WITH DOT ABOVE + return rune(0x2ac4), true + case "suphsol": // SUPERSET PRECEDING SOLIDUS + return rune(0x27c9), true + case "suphsub": // SUPERSET BESIDE SUBSET + return rune(0x2ad7), true + case "suplarr": // SUPERSET ABOVE LEFTWARDS ARROW + return rune(0x297b), true + case "supmult": // SUPERSET WITH MULTIPLICATION SIGN BELOW + return rune(0x2ac2), true + case "supnE": // SUPERSET OF ABOVE NOT EQUAL TO + return rune(0x2acc), true + case "supne": // SUPERSET OF WITH NOT EQUAL TO + return rune(0x228b), true + case "supplus": // SUPERSET WITH PLUS SIGN BELOW + return rune(0x2ac0), true + case "supset": // SUPERSET OF + return rune(0x2283), true + case "supseteq": // SUPERSET OF OR EQUAL TO + return rune(0x2287), true + case "supseteqq": // SUPERSET OF ABOVE EQUALS SIGN + return rune(0x2ac6), true + case "supsetneq": // SUPERSET OF WITH NOT EQUAL TO + return rune(0x228b), true + case "supsetneqq": // SUPERSET OF ABOVE NOT EQUAL TO + return rune(0x2acc), true + case "supsim": // SUPERSET OF ABOVE TILDE OPERATOR + return rune(0x2ac8), true + case "supsub": // SUPERSET ABOVE SUBSET + return rune(0x2ad4), true + case "supsup": // SUPERSET ABOVE SUPERSET + return rune(0x2ad6), true + case "swArr": // SOUTH WEST DOUBLE ARROW + return rune(0x21d9), true + case "swarhk": // SOUTH WEST ARROW WITH HOOK + return rune(0x2926), true + case "swarr": // SOUTH WEST ARROW + return rune(0x2199), true + case "swarrow": // SOUTH WEST ARROW + return rune(0x2199), true + case "swnwar": // SOUTH WEST ARROW AND NORTH WEST ARROW + return rune(0x292a), true + case "szlig": // LATIN SMALL LETTER SHARP S + return rune(0xdf), true } case 't': switch name { - case "target": // POSITION INDICATOR - return rune(0x2316), true - case "tau": // GREEK SMALL LETTER TAU - return rune(0x03c4), true - case "tbrk": // TOP SQUARE BRACKET - return rune(0x23b4), true - case "tcaron": // LATIN SMALL LETTER T WITH CARON - return rune(0x0165), true - case "tcedil": // LATIN SMALL LETTER T WITH CEDILLA - return rune(0x0163), true - case "tcy": // CYRILLIC SMALL LETTER TE - return rune(0x0442), true - case "tdot": // COMBINING THREE DOTS ABOVE - return rune(0x20db), true - case "telrec": // TELEPHONE RECORDER - return rune(0x2315), true - case "tfr": // MATHEMATICAL FRAKTUR SMALL T - return rune(0x01d531), true - case "tgr": // GREEK SMALL LETTER TAU - return rune(0x03c4), true - case "there4": // THEREFORE - return rune(0x2234), true - case "therefore": // THEREFORE - return rune(0x2234), true - case "thermod": // THERMODYNAMIC - return rune(0x29e7), true - case "theta": // GREEK SMALL LETTER THETA - return rune(0x03b8), true - case "thetas": // GREEK SMALL LETTER THETA - return rune(0x03b8), true - case "thetasym": // GREEK THETA SYMBOL - return rune(0x03d1), true - case "thetav": // GREEK THETA SYMBOL - return rune(0x03d1), true - case "thgr": // GREEK SMALL LETTER THETA - return rune(0x03b8), true - case "thickapprox": // ALMOST EQUAL TO - return rune(0x2248), true - case "thicksim": // TILDE OPERATOR - return rune(0x223c), true - case "thinsp": // THIN SPACE - return rune(0x2009), true - case "thkap": // ALMOST EQUAL TO - return rune(0x2248), true - case "thksim": // TILDE OPERATOR - return rune(0x223c), true - case "thorn": // LATIN SMALL LETTER THORN - return rune(0xfe), true - case "tilde": // SMALL TILDE - return rune(0x02dc), true - case "timeint": // INTEGRAL WITH TIMES SIGN - return rune(0x2a18), true - case "times": // MULTIPLICATION SIGN - return rune(0xd7), true - case "timesb": // SQUARED TIMES - return rune(0x22a0), true - case "timesbar": // MULTIPLICATION SIGN WITH UNDERBAR - return rune(0x2a31), true - case "timesd": // MULTIPLICATION SIGN WITH DOT ABOVE - return rune(0x2a30), true - case "tint": // TRIPLE INTEGRAL - return rune(0x222d), true - case "toea": // NORTH EAST ARROW AND SOUTH EAST ARROW - return rune(0x2928), true - case "top": // DOWN TACK - return rune(0x22a4), true - case "topbot": // APL FUNCTIONAL SYMBOL I-BEAM - return rune(0x2336), true - case "topcir": // DOWN TACK WITH CIRCLE BELOW - return rune(0x2af1), true - case "topf": // MATHEMATICAL DOUBLE-STRUCK SMALL T - return rune(0x01d565), true - case "topfork": // PITCHFORK WITH TEE TOP - return rune(0x2ada), true - case "tosa": // SOUTH EAST ARROW AND SOUTH WEST ARROW - return rune(0x2929), true - case "tprime": // TRIPLE PRIME - return rune(0x2034), true - case "trade": // TRADE MARK SIGN - return rune(0x2122), true - case "triS": // S IN TRIANGLE - return rune(0x29cc), true - case "triangle": // WHITE UP-POINTING SMALL TRIANGLE - return rune(0x25b5), true - case "triangledown": // WHITE DOWN-POINTING SMALL TRIANGLE - return rune(0x25bf), true - case "triangleleft": // WHITE LEFT-POINTING SMALL TRIANGLE - return rune(0x25c3), true - case "trianglelefteq": // NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22b4), true - case "triangleq": // DELTA EQUAL TO - return rune(0x225c), true - case "triangleright": // WHITE RIGHT-POINTING SMALL TRIANGLE - return rune(0x25b9), true - case "trianglerighteq": // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO - return rune(0x22b5), true - case "tribar": // TRIANGLE WITH UNDERBAR - return rune(0x29cb), true - case "tridot": // WHITE UP-POINTING TRIANGLE WITH DOT - return rune(0x25ec), true - case "tridoto": // TRIANGLE WITH DOT ABOVE - return rune(0x29ca), true - case "trie": // DELTA EQUAL TO - return rune(0x225c), true - case "triminus": // MINUS SIGN IN TRIANGLE - return rune(0x2a3a), true - case "triplus": // PLUS SIGN IN TRIANGLE - return rune(0x2a39), true - case "trisb": // TRIANGLE WITH SERIFS AT BOTTOM - return rune(0x29cd), true - case "tritime": // MULTIPLICATION SIGN IN TRIANGLE - return rune(0x2a3b), true - case "trpezium": // WHITE TRAPEZIUM - return rune(0x23e2), true - case "tscr": // MATHEMATICAL SCRIPT SMALL T - return rune(0x01d4c9), true - case "tscy": // CYRILLIC SMALL LETTER TSE - return rune(0x0446), true - case "tshcy": // CYRILLIC SMALL LETTER TSHE - return rune(0x045b), true - case "tstrok": // LATIN SMALL LETTER T WITH STROKE - return rune(0x0167), true - case "tverbar": // TRIPLE VERTICAL BAR DELIMITER - return rune(0x2980), true - case "twixt": // BETWEEN - return rune(0x226c), true - case "twoheadleftarrow": // LEFTWARDS TWO HEADED ARROW - return rune(0x219e), true - case "twoheadrightarrow": // RIGHTWARDS TWO HEADED ARROW - return rune(0x21a0), true + case "target": // POSITION INDICATOR + return rune(0x2316), true + case "tau": // GREEK SMALL LETTER TAU + return rune(0x03c4), true + case "tbrk": // TOP SQUARE BRACKET + return rune(0x23b4), true + case "tcaron": // LATIN SMALL LETTER T WITH CARON + return rune(0x0165), true + case "tcedil": // LATIN SMALL LETTER T WITH CEDILLA + return rune(0x0163), true + case "tcy": // CYRILLIC SMALL LETTER TE + return rune(0x0442), true + case "tdot": // COMBINING THREE DOTS ABOVE + return rune(0x20db), true + case "telrec": // TELEPHONE RECORDER + return rune(0x2315), true + case "tfr": // MATHEMATICAL FRAKTUR SMALL T + return rune(0x01d531), true + case "tgr": // GREEK SMALL LETTER TAU + return rune(0x03c4), true + case "there4": // THEREFORE + return rune(0x2234), true + case "therefore": // THEREFORE + return rune(0x2234), true + case "thermod": // THERMODYNAMIC + return rune(0x29e7), true + case "theta": // GREEK SMALL LETTER THETA + return rune(0x03b8), true + case "thetas": // GREEK SMALL LETTER THETA + return rune(0x03b8), true + case "thetasym": // GREEK THETA SYMBOL + return rune(0x03d1), true + case "thetav": // GREEK THETA SYMBOL + return rune(0x03d1), true + case "thgr": // GREEK SMALL LETTER THETA + return rune(0x03b8), true + case "thickapprox": // ALMOST EQUAL TO + return rune(0x2248), true + case "thicksim": // TILDE OPERATOR + return rune(0x223c), true + case "thinsp": // THIN SPACE + return rune(0x2009), true + case "thkap": // ALMOST EQUAL TO + return rune(0x2248), true + case "thksim": // TILDE OPERATOR + return rune(0x223c), true + case "thorn": // LATIN SMALL LETTER THORN + return rune(0xfe), true + case "tilde": // SMALL TILDE + return rune(0x02dc), true + case "timeint": // INTEGRAL WITH TIMES SIGN + return rune(0x2a18), true + case "times": // MULTIPLICATION SIGN + return rune(0xd7), true + case "timesb": // SQUARED TIMES + return rune(0x22a0), true + case "timesbar": // MULTIPLICATION SIGN WITH UNDERBAR + return rune(0x2a31), true + case "timesd": // MULTIPLICATION SIGN WITH DOT ABOVE + return rune(0x2a30), true + case "tint": // TRIPLE INTEGRAL + return rune(0x222d), true + case "toea": // NORTH EAST ARROW AND SOUTH EAST ARROW + return rune(0x2928), true + case "top": // DOWN TACK + return rune(0x22a4), true + case "topbot": // APL FUNCTIONAL SYMBOL I-BEAM + return rune(0x2336), true + case "topcir": // DOWN TACK WITH CIRCLE BELOW + return rune(0x2af1), true + case "topf": // MATHEMATICAL DOUBLE-STRUCK SMALL T + return rune(0x01d565), true + case "topfork": // PITCHFORK WITH TEE TOP + return rune(0x2ada), true + case "tosa": // SOUTH EAST ARROW AND SOUTH WEST ARROW + return rune(0x2929), true + case "tprime": // TRIPLE PRIME + return rune(0x2034), true + case "trade": // TRADE MARK SIGN + return rune(0x2122), true + case "triS": // S IN TRIANGLE + return rune(0x29cc), true + case "triangle": // WHITE UP-POINTING SMALL TRIANGLE + return rune(0x25b5), true + case "triangledown": // WHITE DOWN-POINTING SMALL TRIANGLE + return rune(0x25bf), true + case "triangleleft": // WHITE LEFT-POINTING SMALL TRIANGLE + return rune(0x25c3), true + case "trianglelefteq": // NORMAL SUBGROUP OF OR EQUAL TO + return rune(0x22b4), true + case "triangleq": // DELTA EQUAL TO + return rune(0x225c), true + case "triangleright": // WHITE RIGHT-POINTING SMALL TRIANGLE + return rune(0x25b9), true + case "trianglerighteq": // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO + return rune(0x22b5), true + case "tribar": // TRIANGLE WITH UNDERBAR + return rune(0x29cb), true + case "tridot": // WHITE UP-POINTING TRIANGLE WITH DOT + return rune(0x25ec), true + case "tridoto": // TRIANGLE WITH DOT ABOVE + return rune(0x29ca), true + case "trie": // DELTA EQUAL TO + return rune(0x225c), true + case "triminus": // MINUS SIGN IN TRIANGLE + return rune(0x2a3a), true + case "triplus": // PLUS SIGN IN TRIANGLE + return rune(0x2a39), true + case "trisb": // TRIANGLE WITH SERIFS AT BOTTOM + return rune(0x29cd), true + case "tritime": // MULTIPLICATION SIGN IN TRIANGLE + return rune(0x2a3b), true + case "trpezium": // WHITE TRAPEZIUM + return rune(0x23e2), true + case "tscr": // MATHEMATICAL SCRIPT SMALL T + return rune(0x01d4c9), true + case "tscy": // CYRILLIC SMALL LETTER TSE + return rune(0x0446), true + case "tshcy": // CYRILLIC SMALL LETTER TSHE + return rune(0x045b), true + case "tstrok": // LATIN SMALL LETTER T WITH STROKE + return rune(0x0167), true + case "tverbar": // TRIPLE VERTICAL BAR DELIMITER + return rune(0x2980), true + case "twixt": // BETWEEN + return rune(0x226c), true + case "twoheadleftarrow": // LEFTWARDS TWO HEADED ARROW + return rune(0x219e), true + case "twoheadrightarrow": // RIGHTWARDS TWO HEADED ARROW + return rune(0x21a0), true } case 'u': switch name { - case "uAarr": // UPWARDS TRIPLE ARROW - return rune(0x290a), true - case "uArr": // UPWARDS DOUBLE ARROW - return rune(0x21d1), true - case "uHar": // UPWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT - return rune(0x2963), true - case "uacgr": // GREEK SMALL LETTER UPSILON WITH TONOS - return rune(0x03cd), true - case "uacute": // LATIN SMALL LETTER U WITH ACUTE - return rune(0xfa), true - case "uarr": // UPWARDS ARROW - return rune(0x2191), true - case "uarr2": // UPWARDS PAIRED ARROWS - return rune(0x21c8), true - case "uarrb": // UPWARDS ARROW TO BAR - return rune(0x2912), true - case "uarrln": // UPWARDS ARROW WITH HORIZONTAL STROKE - return rune(0x2909), true - case "ubrcy": // CYRILLIC SMALL LETTER SHORT U - return rune(0x045e), true - case "ubreve": // LATIN SMALL LETTER U WITH BREVE - return rune(0x016d), true - case "ucirc": // LATIN SMALL LETTER U WITH CIRCUMFLEX - return rune(0xfb), true - case "ucy": // CYRILLIC SMALL LETTER U - return rune(0x0443), true - case "udarr": // UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW - return rune(0x21c5), true - case "udblac": // LATIN SMALL LETTER U WITH DOUBLE ACUTE - return rune(0x0171), true - case "udhar": // UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT - return rune(0x296e), true - case "udiagr": // GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS - return rune(0x03b0), true - case "udigr": // GREEK SMALL LETTER UPSILON WITH DIALYTIKA - return rune(0x03cb), true - case "udrbrk": // BOTTOM SQUARE BRACKET - return rune(0x23b5), true - case "udrcub": // BOTTOM CURLY BRACKET - return rune(0x23df), true - case "udrpar": // BOTTOM PARENTHESIS - return rune(0x23dd), true - case "ufisht": // UP FISH TAIL - return rune(0x297e), true - case "ufr": // MATHEMATICAL FRAKTUR SMALL U - return rune(0x01d532), true - case "ugr": // GREEK SMALL LETTER UPSILON - return rune(0x03c5), true - case "ugrave": // LATIN SMALL LETTER U WITH GRAVE - return rune(0xf9), true - case "uharl": // UPWARDS HARPOON WITH BARB LEFTWARDS - return rune(0x21bf), true - case "uharr": // UPWARDS HARPOON WITH BARB RIGHTWARDS - return rune(0x21be), true - case "uhblk": // UPPER HALF BLOCK - return rune(0x2580), true - case "ulcorn": // TOP LEFT CORNER - return rune(0x231c), true - case "ulcorner": // TOP LEFT CORNER - return rune(0x231c), true - case "ulcrop": // TOP LEFT CROP - return rune(0x230f), true - case "uldlshar": // UP BARB LEFT DOWN BARB LEFT HARPOON - return rune(0x2951), true - case "ulharb": // UPWARDS HARPOON WITH BARB LEFT TO BAR - return rune(0x2958), true - case "ultri": // UPPER LEFT TRIANGLE - return rune(0x25f8), true - case "umacr": // LATIN SMALL LETTER U WITH MACRON - return rune(0x016b), true - case "uml": // DIAERESIS - return rune(0xa8), true - case "uogon": // LATIN SMALL LETTER U WITH OGONEK - return rune(0x0173), true - case "uopf": // MATHEMATICAL DOUBLE-STRUCK SMALL U - return rune(0x01d566), true - case "uparrow": // UPWARDS ARROW - return rune(0x2191), true - case "updownarrow": // UP DOWN ARROW - return rune(0x2195), true - case "upharpoonleft": // UPWARDS HARPOON WITH BARB LEFTWARDS - return rune(0x21bf), true - case "upharpoonright": // UPWARDS HARPOON WITH BARB RIGHTWARDS - return rune(0x21be), true - case "upint": // INTEGRAL WITH OVERBAR - return rune(0x2a1b), true - case "uplus": // MULTISET UNION - return rune(0x228e), true - case "upsi": // GREEK SMALL LETTER UPSILON - return rune(0x03c5), true - case "upsih": // GREEK UPSILON WITH HOOK SYMBOL - return rune(0x03d2), true - case "upsilon": // GREEK SMALL LETTER UPSILON - return rune(0x03c5), true - case "upuparrows": // UPWARDS PAIRED ARROWS - return rune(0x21c8), true - case "urcorn": // TOP RIGHT CORNER - return rune(0x231d), true - case "urcorner": // TOP RIGHT CORNER - return rune(0x231d), true - case "urcrop": // TOP RIGHT CROP - return rune(0x230e), true - case "urdrshar": // UP BARB RIGHT DOWN BARB RIGHT HARPOON - return rune(0x294f), true - case "urharb": // UPWARDS HARPOON WITH BARB RIGHT TO BAR - return rune(0x2954), true - case "uring": // LATIN SMALL LETTER U WITH RING ABOVE - return rune(0x016f), true - case "urtri": // UPPER RIGHT TRIANGLE - return rune(0x25f9), true - case "urtrif": // BLACK UPPER RIGHT TRIANGLE - return rune(0x25e5), true - case "uscr": // MATHEMATICAL SCRIPT SMALL U - return rune(0x01d4ca), true - case "utdot": // UP RIGHT DIAGONAL ELLIPSIS - return rune(0x22f0), true - case "utilde": // LATIN SMALL LETTER U WITH TILDE - return rune(0x0169), true - case "utri": // WHITE UP-POINTING SMALL TRIANGLE - return rune(0x25b5), true - case "utrif": // BLACK UP-POINTING SMALL TRIANGLE - return rune(0x25b4), true - case "uuarr": // UPWARDS PAIRED ARROWS - return rune(0x21c8), true - case "uuml": // LATIN SMALL LETTER U WITH DIAERESIS - return rune(0xfc), true - case "uwangle": // OBLIQUE ANGLE OPENING DOWN - return rune(0x29a7), true + case "uAarr": // UPWARDS TRIPLE ARROW + return rune(0x290a), true + case "uArr": // UPWARDS DOUBLE ARROW + return rune(0x21d1), true + case "uHar": // UPWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT + return rune(0x2963), true + case "uacgr": // GREEK SMALL LETTER UPSILON WITH TONOS + return rune(0x03cd), true + case "uacute": // LATIN SMALL LETTER U WITH ACUTE + return rune(0xfa), true + case "uarr": // UPWARDS ARROW + return rune(0x2191), true + case "uarr2": // UPWARDS PAIRED ARROWS + return rune(0x21c8), true + case "uarrb": // UPWARDS ARROW TO BAR + return rune(0x2912), true + case "uarrln": // UPWARDS ARROW WITH HORIZONTAL STROKE + return rune(0x2909), true + case "ubrcy": // CYRILLIC SMALL LETTER SHORT U + return rune(0x045e), true + case "ubreve": // LATIN SMALL LETTER U WITH BREVE + return rune(0x016d), true + case "ucirc": // LATIN SMALL LETTER U WITH CIRCUMFLEX + return rune(0xfb), true + case "ucy": // CYRILLIC SMALL LETTER U + return rune(0x0443), true + case "udarr": // UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW + return rune(0x21c5), true + case "udblac": // LATIN SMALL LETTER U WITH DOUBLE ACUTE + return rune(0x0171), true + case "udhar": // UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT + return rune(0x296e), true + case "udiagr": // GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS + return rune(0x03b0), true + case "udigr": // GREEK SMALL LETTER UPSILON WITH DIALYTIKA + return rune(0x03cb), true + case "udrbrk": // BOTTOM SQUARE BRACKET + return rune(0x23b5), true + case "udrcub": // BOTTOM CURLY BRACKET + return rune(0x23df), true + case "udrpar": // BOTTOM PARENTHESIS + return rune(0x23dd), true + case "ufisht": // UP FISH TAIL + return rune(0x297e), true + case "ufr": // MATHEMATICAL FRAKTUR SMALL U + return rune(0x01d532), true + case "ugr": // GREEK SMALL LETTER UPSILON + return rune(0x03c5), true + case "ugrave": // LATIN SMALL LETTER U WITH GRAVE + return rune(0xf9), true + case "uharl": // UPWARDS HARPOON WITH BARB LEFTWARDS + return rune(0x21bf), true + case "uharr": // UPWARDS HARPOON WITH BARB RIGHTWARDS + return rune(0x21be), true + case "uhblk": // UPPER HALF BLOCK + return rune(0x2580), true + case "ulcorn": // TOP LEFT CORNER + return rune(0x231c), true + case "ulcorner": // TOP LEFT CORNER + return rune(0x231c), true + case "ulcrop": // TOP LEFT CROP + return rune(0x230f), true + case "uldlshar": // UP BARB LEFT DOWN BARB LEFT HARPOON + return rune(0x2951), true + case "ulharb": // UPWARDS HARPOON WITH BARB LEFT TO BAR + return rune(0x2958), true + case "ultri": // UPPER LEFT TRIANGLE + return rune(0x25f8), true + case "umacr": // LATIN SMALL LETTER U WITH MACRON + return rune(0x016b), true + case "uml": // DIAERESIS + return rune(0xa8), true + case "uogon": // LATIN SMALL LETTER U WITH OGONEK + return rune(0x0173), true + case "uopf": // MATHEMATICAL DOUBLE-STRUCK SMALL U + return rune(0x01d566), true + case "uparrow": // UPWARDS ARROW + return rune(0x2191), true + case "updownarrow": // UP DOWN ARROW + return rune(0x2195), true + case "upharpoonleft": // UPWARDS HARPOON WITH BARB LEFTWARDS + return rune(0x21bf), true + case "upharpoonright": // UPWARDS HARPOON WITH BARB RIGHTWARDS + return rune(0x21be), true + case "upint": // INTEGRAL WITH OVERBAR + return rune(0x2a1b), true + case "uplus": // MULTISET UNION + return rune(0x228e), true + case "upsi": // GREEK SMALL LETTER UPSILON + return rune(0x03c5), true + case "upsih": // GREEK UPSILON WITH HOOK SYMBOL + return rune(0x03d2), true + case "upsilon": // GREEK SMALL LETTER UPSILON + return rune(0x03c5), true + case "upuparrows": // UPWARDS PAIRED ARROWS + return rune(0x21c8), true + case "urcorn": // TOP RIGHT CORNER + return rune(0x231d), true + case "urcorner": // TOP RIGHT CORNER + return rune(0x231d), true + case "urcrop": // TOP RIGHT CROP + return rune(0x230e), true + case "urdrshar": // UP BARB RIGHT DOWN BARB RIGHT HARPOON + return rune(0x294f), true + case "urharb": // UPWARDS HARPOON WITH BARB RIGHT TO BAR + return rune(0x2954), true + case "uring": // LATIN SMALL LETTER U WITH RING ABOVE + return rune(0x016f), true + case "urtri": // UPPER RIGHT TRIANGLE + return rune(0x25f9), true + case "urtrif": // BLACK UPPER RIGHT TRIANGLE + return rune(0x25e5), true + case "uscr": // MATHEMATICAL SCRIPT SMALL U + return rune(0x01d4ca), true + case "utdot": // UP RIGHT DIAGONAL ELLIPSIS + return rune(0x22f0), true + case "utilde": // LATIN SMALL LETTER U WITH TILDE + return rune(0x0169), true + case "utri": // WHITE UP-POINTING SMALL TRIANGLE + return rune(0x25b5), true + case "utrif": // BLACK UP-POINTING SMALL TRIANGLE + return rune(0x25b4), true + case "uuarr": // UPWARDS PAIRED ARROWS + return rune(0x21c8), true + case "uuml": // LATIN SMALL LETTER U WITH DIAERESIS + return rune(0xfc), true + case "uwangle": // OBLIQUE ANGLE OPENING DOWN + return rune(0x29a7), true } case 'v': switch name { - case "vArr": // UP DOWN DOUBLE ARROW - return rune(0x21d5), true - case "vBar": // SHORT UP TACK WITH UNDERBAR - return rune(0x2ae8), true - case "vBarv": // SHORT UP TACK ABOVE SHORT DOWN TACK - return rune(0x2ae9), true - case "vDash": // TRUE - return rune(0x22a8), true - case "vDdash": // VERTICAL BAR TRIPLE RIGHT TURNSTILE - return rune(0x2ae2), true - case "vangrt": // RIGHT ANGLE VARIANT WITH SQUARE - return rune(0x299c), true - case "varepsilon": // GREEK LUNATE EPSILON SYMBOL - return rune(0x03f5), true - case "varkappa": // GREEK KAPPA SYMBOL - return rune(0x03f0), true - case "varnothing": // EMPTY SET - return rune(0x2205), true - case "varphi": // GREEK PHI SYMBOL - return rune(0x03d5), true - case "varpi": // GREEK PI SYMBOL - return rune(0x03d6), true - case "varpropto": // PROPORTIONAL TO - return rune(0x221d), true - case "varr": // UP DOWN ARROW - return rune(0x2195), true - case "varrho": // GREEK RHO SYMBOL - return rune(0x03f1), true - case "varsigma": // GREEK SMALL LETTER FINAL SIGMA - return rune(0x03c2), true - case "varsubsetneq": // SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members - return rune(0x228a), true - case "varsubsetneqq": // SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members - return rune(0x2acb), true - case "varsupsetneq": // SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members - return rune(0x228b), true - case "varsupsetneqq": // SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members - return rune(0x2acc), true - case "vartheta": // GREEK THETA SYMBOL - return rune(0x03d1), true - case "vartriangleleft": // NORMAL SUBGROUP OF - return rune(0x22b2), true - case "vartriangleright": // CONTAINS AS NORMAL SUBGROUP - return rune(0x22b3), true - case "vbrtri": // VERTICAL BAR BESIDE RIGHT TRIANGLE - return rune(0x29d0), true - case "vcy": // CYRILLIC SMALL LETTER VE - return rune(0x0432), true - case "vdash": // RIGHT TACK - return rune(0x22a2), true - case "vee": // LOGICAL OR - return rune(0x2228), true - case "veeBar": // LOGICAL OR WITH DOUBLE UNDERBAR - return rune(0x2a63), true - case "veebar": // XOR - return rune(0x22bb), true - case "veeeq": // EQUIANGULAR TO - return rune(0x225a), true - case "vellip": // VERTICAL ELLIPSIS - return rune(0x22ee), true - case "vellip4": // DOTTED FENCE - return rune(0x2999), true - case "vellipv": // TRIPLE COLON OPERATOR - return rune(0x2af6), true - case "verbar": // VERTICAL LINE - return rune(0x7c), true - case "vert": // VERTICAL LINE - return rune(0x7c), true - case "vert3": // TRIPLE VERTICAL BAR BINARY RELATION - return rune(0x2af4), true - case "vfr": // MATHEMATICAL FRAKTUR SMALL V - return rune(0x01d533), true - case "vldash": // LEFT SQUARE BRACKET LOWER CORNER - return rune(0x23a3), true - case "vltri": // NORMAL SUBGROUP OF - return rune(0x22b2), true - case "vnsub": // SUBSET OF with vertical line - return rune(0x2282), true - case "vnsup": // SUPERSET OF with vertical line - return rune(0x2283), true - case "vopf": // MATHEMATICAL DOUBLE-STRUCK SMALL V - return rune(0x01d567), true - case "vprime": // PRIME - return rune(0x2032), true - case "vprop": // PROPORTIONAL TO - return rune(0x221d), true - case "vrtri": // CONTAINS AS NORMAL SUBGROUP - return rune(0x22b3), true - case "vscr": // MATHEMATICAL SCRIPT SMALL V - return rune(0x01d4cb), true - case "vsubnE": // SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members - return rune(0x2acb), true - case "vsubne": // SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members - return rune(0x228a), true - case "vsupnE": // SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members - return rune(0x2acc), true - case "vsupne": // SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members - return rune(0x228b), true - case "vzigzag": // VERTICAL ZIGZAG LINE - return rune(0x299a), true + case "vArr": // UP DOWN DOUBLE ARROW + return rune(0x21d5), true + case "vBar": // SHORT UP TACK WITH UNDERBAR + return rune(0x2ae8), true + case "vBarv": // SHORT UP TACK ABOVE SHORT DOWN TACK + return rune(0x2ae9), true + case "vDash": // TRUE + return rune(0x22a8), true + case "vDdash": // VERTICAL BAR TRIPLE RIGHT TURNSTILE + return rune(0x2ae2), true + case "vangrt": // RIGHT ANGLE VARIANT WITH SQUARE + return rune(0x299c), true + case "varepsilon": // GREEK LUNATE EPSILON SYMBOL + return rune(0x03f5), true + case "varkappa": // GREEK KAPPA SYMBOL + return rune(0x03f0), true + case "varnothing": // EMPTY SET + return rune(0x2205), true + case "varphi": // GREEK PHI SYMBOL + return rune(0x03d5), true + case "varpi": // GREEK PI SYMBOL + return rune(0x03d6), true + case "varpropto": // PROPORTIONAL TO + return rune(0x221d), true + case "varr": // UP DOWN ARROW + return rune(0x2195), true + case "varrho": // GREEK RHO SYMBOL + return rune(0x03f1), true + case "varsigma": // GREEK SMALL LETTER FINAL SIGMA + return rune(0x03c2), true + case "varsubsetneq": // SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members + return rune(0x228a), true + case "varsubsetneqq": // SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members + return rune(0x2acb), true + case "varsupsetneq": // SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members + return rune(0x228b), true + case "varsupsetneqq": // SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members + return rune(0x2acc), true + case "vartheta": // GREEK THETA SYMBOL + return rune(0x03d1), true + case "vartriangleleft": // NORMAL SUBGROUP OF + return rune(0x22b2), true + case "vartriangleright": // CONTAINS AS NORMAL SUBGROUP + return rune(0x22b3), true + case "vbrtri": // VERTICAL BAR BESIDE RIGHT TRIANGLE + return rune(0x29d0), true + case "vcy": // CYRILLIC SMALL LETTER VE + return rune(0x0432), true + case "vdash": // RIGHT TACK + return rune(0x22a2), true + case "vee": // LOGICAL OR + return rune(0x2228), true + case "veeBar": // LOGICAL OR WITH DOUBLE UNDERBAR + return rune(0x2a63), true + case "veebar": // XOR + return rune(0x22bb), true + case "veeeq": // EQUIANGULAR TO + return rune(0x225a), true + case "vellip": // VERTICAL ELLIPSIS + return rune(0x22ee), true + case "vellip4": // DOTTED FENCE + return rune(0x2999), true + case "vellipv": // TRIPLE COLON OPERATOR + return rune(0x2af6), true + case "verbar": // VERTICAL LINE + return rune(0x7c), true + case "vert": // VERTICAL LINE + return rune(0x7c), true + case "vert3": // TRIPLE VERTICAL BAR BINARY RELATION + return rune(0x2af4), true + case "vfr": // MATHEMATICAL FRAKTUR SMALL V + return rune(0x01d533), true + case "vldash": // LEFT SQUARE BRACKET LOWER CORNER + return rune(0x23a3), true + case "vltri": // NORMAL SUBGROUP OF + return rune(0x22b2), true + case "vnsub": // SUBSET OF with vertical line + return rune(0x2282), true + case "vnsup": // SUPERSET OF with vertical line + return rune(0x2283), true + case "vopf": // MATHEMATICAL DOUBLE-STRUCK SMALL V + return rune(0x01d567), true + case "vprime": // PRIME + return rune(0x2032), true + case "vprop": // PROPORTIONAL TO + return rune(0x221d), true + case "vrtri": // CONTAINS AS NORMAL SUBGROUP + return rune(0x22b3), true + case "vscr": // MATHEMATICAL SCRIPT SMALL V + return rune(0x01d4cb), true + case "vsubnE": // SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members + return rune(0x2acb), true + case "vsubne": // SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members + return rune(0x228a), true + case "vsupnE": // SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members + return rune(0x2acc), true + case "vsupne": // SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members + return rune(0x228b), true + case "vzigzag": // VERTICAL ZIGZAG LINE + return rune(0x299a), true } case 'w': switch name { - case "wcirc": // LATIN SMALL LETTER W WITH CIRCUMFLEX - return rune(0x0175), true - case "wedbar": // LOGICAL AND WITH UNDERBAR - return rune(0x2a5f), true - case "wedge": // LOGICAL AND - return rune(0x2227), true - case "wedgeq": // ESTIMATES - return rune(0x2259), true - case "weierp": // SCRIPT CAPITAL P - return rune(0x2118), true - case "wfr": // MATHEMATICAL FRAKTUR SMALL W - return rune(0x01d534), true - case "wopf": // MATHEMATICAL DOUBLE-STRUCK SMALL W - return rune(0x01d568), true - case "wp": // SCRIPT CAPITAL P - return rune(0x2118), true - case "wr": // WREATH PRODUCT - return rune(0x2240), true - case "wreath": // WREATH PRODUCT - return rune(0x2240), true - case "wscr": // MATHEMATICAL SCRIPT SMALL W - return rune(0x01d4cc), true + case "wcirc": // LATIN SMALL LETTER W WITH CIRCUMFLEX + return rune(0x0175), true + case "wedbar": // LOGICAL AND WITH UNDERBAR + return rune(0x2a5f), true + case "wedge": // LOGICAL AND + return rune(0x2227), true + case "wedgeq": // ESTIMATES + return rune(0x2259), true + case "weierp": // SCRIPT CAPITAL P + return rune(0x2118), true + case "wfr": // MATHEMATICAL FRAKTUR SMALL W + return rune(0x01d534), true + case "wopf": // MATHEMATICAL DOUBLE-STRUCK SMALL W + return rune(0x01d568), true + case "wp": // SCRIPT CAPITAL P + return rune(0x2118), true + case "wr": // WREATH PRODUCT + return rune(0x2240), true + case "wreath": // WREATH PRODUCT + return rune(0x2240), true + case "wscr": // MATHEMATICAL SCRIPT SMALL W + return rune(0x01d4cc), true } case 'x': switch name { - case "xandand": // TWO LOGICAL AND OPERATOR - return rune(0x2a07), true - case "xbsol": // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT - return rune(0x2571), true - case "xcap": // N-ARY INTERSECTION - return rune(0x22c2), true - case "xcirc": // LARGE CIRCLE - return rune(0x25ef), true - case "xcup": // N-ARY UNION - return rune(0x22c3), true - case "xcupdot": // N-ARY UNION OPERATOR WITH DOT - return rune(0x2a03), true - case "xdtri": // WHITE DOWN-POINTING TRIANGLE - return rune(0x25bd), true - case "xfr": // MATHEMATICAL FRAKTUR SMALL X - return rune(0x01d535), true - case "xgr": // GREEK SMALL LETTER XI - return rune(0x03be), true - case "xhArr": // LONG LEFT RIGHT DOUBLE ARROW - return rune(0x27fa), true - case "xharr": // LONG LEFT RIGHT ARROW - return rune(0x27f7), true - case "xi": // GREEK SMALL LETTER XI - return rune(0x03be), true - case "xlArr": // LONG LEFTWARDS DOUBLE ARROW - return rune(0x27f8), true - case "xlarr": // LONG LEFTWARDS ARROW - return rune(0x27f5), true - case "xmap": // LONG RIGHTWARDS ARROW FROM BAR - return rune(0x27fc), true - case "xnis": // CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE - return rune(0x22fb), true - case "xodot": // N-ARY CIRCLED DOT OPERATOR - return rune(0x2a00), true - case "xopf": // MATHEMATICAL DOUBLE-STRUCK SMALL X - return rune(0x01d569), true - case "xoplus": // N-ARY CIRCLED PLUS OPERATOR - return rune(0x2a01), true - case "xoror": // TWO LOGICAL OR OPERATOR - return rune(0x2a08), true - case "xotime": // N-ARY CIRCLED TIMES OPERATOR - return rune(0x2a02), true - case "xrArr": // LONG RIGHTWARDS DOUBLE ARROW - return rune(0x27f9), true - case "xrarr": // LONG RIGHTWARDS ARROW - return rune(0x27f6), true - case "xscr": // MATHEMATICAL SCRIPT SMALL X - return rune(0x01d4cd), true - case "xsol": // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT - return rune(0x2572), true - case "xsqcap": // N-ARY SQUARE INTERSECTION OPERATOR - return rune(0x2a05), true - case "xsqcup": // N-ARY SQUARE UNION OPERATOR - return rune(0x2a06), true - case "xsqu": // WHITE MEDIUM SQUARE - return rune(0x25fb), true - case "xsquf": // BLACK MEDIUM SQUARE - return rune(0x25fc), true - case "xtimes": // N-ARY TIMES OPERATOR - return rune(0x2a09), true - case "xuplus": // N-ARY UNION OPERATOR WITH PLUS - return rune(0x2a04), true - case "xutri": // WHITE UP-POINTING TRIANGLE - return rune(0x25b3), true - case "xvee": // N-ARY LOGICAL OR - return rune(0x22c1), true - case "xwedge": // N-ARY LOGICAL AND - return rune(0x22c0), true + case "xandand": // TWO LOGICAL AND OPERATOR + return rune(0x2a07), true + case "xbsol": // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT + return rune(0x2571), true + case "xcap": // N-ARY INTERSECTION + return rune(0x22c2), true + case "xcirc": // LARGE CIRCLE + return rune(0x25ef), true + case "xcup": // N-ARY UNION + return rune(0x22c3), true + case "xcupdot": // N-ARY UNION OPERATOR WITH DOT + return rune(0x2a03), true + case "xdtri": // WHITE DOWN-POINTING TRIANGLE + return rune(0x25bd), true + case "xfr": // MATHEMATICAL FRAKTUR SMALL X + return rune(0x01d535), true + case "xgr": // GREEK SMALL LETTER XI + return rune(0x03be), true + case "xhArr": // LONG LEFT RIGHT DOUBLE ARROW + return rune(0x27fa), true + case "xharr": // LONG LEFT RIGHT ARROW + return rune(0x27f7), true + case "xi": // GREEK SMALL LETTER XI + return rune(0x03be), true + case "xlArr": // LONG LEFTWARDS DOUBLE ARROW + return rune(0x27f8), true + case "xlarr": // LONG LEFTWARDS ARROW + return rune(0x27f5), true + case "xmap": // LONG RIGHTWARDS ARROW FROM BAR + return rune(0x27fc), true + case "xnis": // CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE + return rune(0x22fb), true + case "xodot": // N-ARY CIRCLED DOT OPERATOR + return rune(0x2a00), true + case "xopf": // MATHEMATICAL DOUBLE-STRUCK SMALL X + return rune(0x01d569), true + case "xoplus": // N-ARY CIRCLED PLUS OPERATOR + return rune(0x2a01), true + case "xoror": // TWO LOGICAL OR OPERATOR + return rune(0x2a08), true + case "xotime": // N-ARY CIRCLED TIMES OPERATOR + return rune(0x2a02), true + case "xrArr": // LONG RIGHTWARDS DOUBLE ARROW + return rune(0x27f9), true + case "xrarr": // LONG RIGHTWARDS ARROW + return rune(0x27f6), true + case "xscr": // MATHEMATICAL SCRIPT SMALL X + return rune(0x01d4cd), true + case "xsol": // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT + return rune(0x2572), true + case "xsqcap": // N-ARY SQUARE INTERSECTION OPERATOR + return rune(0x2a05), true + case "xsqcup": // N-ARY SQUARE UNION OPERATOR + return rune(0x2a06), true + case "xsqu": // WHITE MEDIUM SQUARE + return rune(0x25fb), true + case "xsquf": // BLACK MEDIUM SQUARE + return rune(0x25fc), true + case "xtimes": // N-ARY TIMES OPERATOR + return rune(0x2a09), true + case "xuplus": // N-ARY UNION OPERATOR WITH PLUS + return rune(0x2a04), true + case "xutri": // WHITE UP-POINTING TRIANGLE + return rune(0x25b3), true + case "xvee": // N-ARY LOGICAL OR + return rune(0x22c1), true + case "xwedge": // N-ARY LOGICAL AND + return rune(0x22c0), true } case 'y': switch name { - case "yacute": // LATIN SMALL LETTER Y WITH ACUTE - return rune(0xfd), true - case "yacy": // CYRILLIC SMALL LETTER YA - return rune(0x044f), true - case "ycirc": // LATIN SMALL LETTER Y WITH CIRCUMFLEX - return rune(0x0177), true - case "ycy": // CYRILLIC SMALL LETTER YERU - return rune(0x044b), true - case "yen": // YEN SIGN - return rune(0xa5), true - case "yfr": // MATHEMATICAL FRAKTUR SMALL Y - return rune(0x01d536), true - case "yicy": // CYRILLIC SMALL LETTER YI - return rune(0x0457), true - case "yopf": // MATHEMATICAL DOUBLE-STRUCK SMALL Y - return rune(0x01d56a), true - case "yscr": // MATHEMATICAL SCRIPT SMALL Y - return rune(0x01d4ce), true - case "yucy": // CYRILLIC SMALL LETTER YU - return rune(0x044e), true - case "yuml": // LATIN SMALL LETTER Y WITH DIAERESIS - return rune(0xff), true + case "yacute": // LATIN SMALL LETTER Y WITH ACUTE + return rune(0xfd), true + case "yacy": // CYRILLIC SMALL LETTER YA + return rune(0x044f), true + case "ycirc": // LATIN SMALL LETTER Y WITH CIRCUMFLEX + return rune(0x0177), true + case "ycy": // CYRILLIC SMALL LETTER YERU + return rune(0x044b), true + case "yen": // YEN SIGN + return rune(0xa5), true + case "yfr": // MATHEMATICAL FRAKTUR SMALL Y + return rune(0x01d536), true + case "yicy": // CYRILLIC SMALL LETTER YI + return rune(0x0457), true + case "yopf": // MATHEMATICAL DOUBLE-STRUCK SMALL Y + return rune(0x01d56a), true + case "yscr": // MATHEMATICAL SCRIPT SMALL Y + return rune(0x01d4ce), true + case "yucy": // CYRILLIC SMALL LETTER YU + return rune(0x044e), true + case "yuml": // LATIN SMALL LETTER Y WITH DIAERESIS + return rune(0xff), true } case 'z': switch name { - case "zacute": // LATIN SMALL LETTER Z WITH ACUTE - return rune(0x017a), true - case "zcaron": // LATIN SMALL LETTER Z WITH CARON - return rune(0x017e), true - case "zcy": // CYRILLIC SMALL LETTER ZE - return rune(0x0437), true - case "zdot": // LATIN SMALL LETTER Z WITH DOT ABOVE - return rune(0x017c), true - case "zeetrf": // BLACK-LETTER CAPITAL Z - return rune(0x2128), true - case "zeta": // GREEK SMALL LETTER ZETA - return rune(0x03b6), true - case "zfr": // MATHEMATICAL FRAKTUR SMALL Z - return rune(0x01d537), true - case "zgr": // GREEK SMALL LETTER ZETA - return rune(0x03b6), true - case "zhcy": // CYRILLIC SMALL LETTER ZHE - return rune(0x0436), true - case "zigrarr": // RIGHTWARDS SQUIGGLE ARROW - return rune(0x21dd), true - case "zopf": // MATHEMATICAL DOUBLE-STRUCK SMALL Z - return rune(0x01d56b), true - case "zscr": // MATHEMATICAL SCRIPT SMALL Z - return rune(0x01d4cf), true - case "zwj": // ZERO WIDTH JOINER - return rune(0x200d), true - case "zwnj": // ZERO WIDTH NON-JOINER - return rune(0x200c), true + case "zacute": // LATIN SMALL LETTER Z WITH ACUTE + return rune(0x017a), true + case "zcaron": // LATIN SMALL LETTER Z WITH CARON + return rune(0x017e), true + case "zcy": // CYRILLIC SMALL LETTER ZE + return rune(0x0437), true + case "zdot": // LATIN SMALL LETTER Z WITH DOT ABOVE + return rune(0x017c), true + case "zeetrf": // BLACK-LETTER CAPITAL Z + return rune(0x2128), true + case "zeta": // GREEK SMALL LETTER ZETA + return rune(0x03b6), true + case "zfr": // MATHEMATICAL FRAKTUR SMALL Z + return rune(0x01d537), true + case "zgr": // GREEK SMALL LETTER ZETA + return rune(0x03b6), true + case "zhcy": // CYRILLIC SMALL LETTER ZHE + return rune(0x0436), true + case "zigrarr": // RIGHTWARDS SQUIGGLE ARROW + return rune(0x21dd), true + case "zopf": // MATHEMATICAL DOUBLE-STRUCK SMALL Z + return rune(0x01d56b), true + case "zscr": // MATHEMATICAL SCRIPT SMALL Z + return rune(0x01d4cf), true + case "zwj": // ZERO WIDTH JOINER + return rune(0x200d), true + case "zwnj": // ZERO WIDTH NON-JOINER + return rune(0x200c), true } } return -1, false diff --git a/core/encoding/hex/hex.odin b/core/encoding/hex/hex.odin index dbffe216b..c2cd89c5b 100644 --- a/core/encoding/hex/hex.odin +++ b/core/encoding/hex/hex.odin @@ -2,8 +2,8 @@ package encoding_hex import "core:strings" -encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds_check { - dst := make([]byte, len(src) * 2, allocator) +encode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> []byte #no_bounds_check { + dst := make([]byte, len(src) * 2, allocator, loc) for i, j := 0, 0; i < len(src); i += 1 { v := src[i] dst[j] = HEXTABLE[v>>4] @@ -15,12 +15,12 @@ encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds } -decode :: proc(src: []byte, allocator := context.allocator) -> (dst: []byte, ok: bool) #no_bounds_check { +decode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> (dst: []byte, ok: bool) #no_bounds_check { if len(src) % 2 == 1 { return } - dst = make([]byte, len(src) / 2, allocator) + dst = make([]byte, len(src) / 2, allocator, loc) for i, j := 0, 1; j < len(src); j += 2 { p := src[j-1] q := src[j] @@ -69,5 +69,4 @@ hex_digit :: proc(char: byte) -> (u8, bool) { case 'A' ..= 'F': return char - 'A' + 10, true case: return 0, false } -} - +} \ No newline at end of file diff --git a/core/encoding/hxa/hxa.odin b/core/encoding/hxa/hxa.odin index 9b24ede9c..9d0c58196 100644 --- a/core/encoding/hxa/hxa.odin +++ b/core/encoding/hxa/hxa.odin @@ -160,34 +160,35 @@ CONVENTION_SOFT_TRANSFORM :: "transform" /* destroy procedures */ -meta_destroy :: proc(meta: Meta, allocator := context.allocator) { +meta_destroy :: proc(meta: Meta, allocator := context.allocator, loc := #caller_location) { if nested, ok := meta.value.([]Meta); ok { for m in nested { - meta_destroy(m) + meta_destroy(m, loc=loc) } - delete(nested, allocator) + delete(nested, allocator, loc=loc) } } -nodes_destroy :: proc(nodes: []Node, allocator := context.allocator) { +nodes_destroy :: proc(nodes: []Node, allocator := context.allocator, loc := #caller_location) { for node in nodes { for meta in node.meta_data { - meta_destroy(meta) + meta_destroy(meta, loc=loc) } - delete(node.meta_data, allocator) + delete(node.meta_data, allocator, loc=loc) switch n in node.content { case Node_Geometry: - delete(n.corner_stack, allocator) - delete(n.edge_stack, allocator) - delete(n.face_stack, allocator) + delete(n.corner_stack, allocator, loc=loc) + delete(n.vertex_stack, allocator, loc=loc) + delete(n.edge_stack, allocator, loc=loc) + delete(n.face_stack, allocator, loc=loc) case Node_Image: - delete(n.image_stack, allocator) + delete(n.image_stack, allocator, loc=loc) } } - delete(nodes, allocator) + delete(nodes, allocator, loc=loc) } -file_destroy :: proc(file: File) { - nodes_destroy(file.nodes, file.allocator) - delete(file.backing, file.allocator) -} +file_destroy :: proc(file: File, loc := #caller_location) { + nodes_destroy(file.nodes, file.allocator, loc=loc) + delete(file.backing, file.allocator, loc=loc) +} \ No newline at end of file diff --git a/core/encoding/hxa/read.odin b/core/encoding/hxa/read.odin index f37dc3193..5c8503229 100644 --- a/core/encoding/hxa/read.odin +++ b/core/encoding/hxa/read.odin @@ -11,24 +11,21 @@ Read_Error :: enum { Unable_To_Read_File, } -read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) { +read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) { context.allocator = allocator - data, ok := os.read_entire_file(filename) + data, ok := os.read_entire_file(filename, allocator, loc) if !ok { err = .Unable_To_Read_File + delete(data, allocator, loc) return } - defer if !ok { - delete(data) - } else { - file.backing = data - } - file, err = read(data, filename, print_error, allocator) + file, err = read(data, filename, print_error, allocator, loc) + file.backing = data return } -read :: proc(data: []byte, filename := "", print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) { +read :: proc(data: []byte, filename := "", print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) { Reader :: struct { filename: string, data: []byte, @@ -79,8 +76,8 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato return string(data[:len]), nil } - read_meta :: proc(r: ^Reader, capacity: u32le) -> (meta_data: []Meta, err: Read_Error) { - meta_data = make([]Meta, int(capacity)) + read_meta :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (meta_data: []Meta, err: Read_Error) { + meta_data = make([]Meta, int(capacity), allocator=allocator) count := 0 defer meta_data = meta_data[:count] for &m in meta_data { @@ -111,10 +108,10 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato return } - read_layer_stack :: proc(r: ^Reader, capacity: u32le) -> (layers: Layer_Stack, err: Read_Error) { + read_layer_stack :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (layers: Layer_Stack, err: Read_Error) { stack_count := read_value(r, u32le) or_return layer_count := 0 - layers = make(Layer_Stack, stack_count) + layers = make(Layer_Stack, stack_count, allocator=allocator, loc=loc) defer layers = layers[:layer_count] for &layer in layers { layer.name = read_name(r) or_return @@ -170,7 +167,8 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato node_count := 0 file.header = header^ - file.nodes = make([]Node, header.internal_node_count) + file.nodes = make([]Node, header.internal_node_count, allocator=allocator, loc=loc) + file.allocator = allocator defer if err != nil { nodes_destroy(file.nodes) file.nodes = nil @@ -198,15 +196,15 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato case .Geometry: g: Node_Geometry - g.vertex_count = read_value(r, u32le) or_return - g.vertex_stack = read_layer_stack(r, g.vertex_count) or_return - g.edge_corner_count = read_value(r, u32le) or_return - g.corner_stack = read_layer_stack(r, g.edge_corner_count) or_return + g.vertex_count = read_value(r, u32le) or_return + g.vertex_stack = read_layer_stack(r, g.vertex_count, loc=loc) or_return + g.edge_corner_count = read_value(r, u32le) or_return + g.corner_stack = read_layer_stack(r, g.edge_corner_count, loc=loc) or_return if header.version > 2 { - g.edge_stack = read_layer_stack(r, g.edge_corner_count) or_return + g.edge_stack = read_layer_stack(r, g.edge_corner_count, loc=loc) or_return } - g.face_count = read_value(r, u32le) or_return - g.face_stack = read_layer_stack(r, g.face_count) or_return + g.face_count = read_value(r, u32le) or_return + g.face_stack = read_layer_stack(r, g.face_count, loc=loc) or_return node.content = g @@ -233,4 +231,4 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato } return -} +} \ No newline at end of file diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin new file mode 100644 index 000000000..d0dd33aba --- /dev/null +++ b/core/encoding/ini/ini.odin @@ -0,0 +1,191 @@ +package encoding_ini + +import "base:runtime" +import "base:intrinsics" +import "core:strings" +import "core:strconv" +import "core:io" +import "core:os" +import "core:fmt" +_ :: fmt + +Options :: struct { + comment: string, + key_lower_case: bool, +} + +DEFAULT_OPTIONS :: Options { + comment = ";", + key_lower_case = false, +} + +Iterator :: struct { + section: string, + _src: string, + options: Options, +} + +iterator_from_string :: proc(src: string, options := DEFAULT_OPTIONS) -> Iterator { + return { + section = "", + options = options, + _src = src, + } +} + + +// Returns the raw `key` and `value`. `ok` will be false if no more key=value pairs cannot be found. +// They key and value may be quoted, which may require the use of `strconv.unquote_string`. +iterate :: proc(it: ^Iterator) -> (key, value: string, ok: bool) { + for line_ in strings.split_lines_iterator(&it._src) { + line := strings.trim_space(line_) + + if len(line) == 0 { + continue + } + + if line[0] == '[' { + end_idx := strings.index_byte(line, ']') + if end_idx < 0 { + end_idx = len(line) + } + it.section = line[1:end_idx] + continue + } + + if it.options.comment != "" && strings.has_prefix(line, it.options.comment) { + continue + } + + equal := strings.index(line, " =") // check for things keys that `ctrl+= = zoom_in` + quote := strings.index_byte(line, '"') + if equal < 0 || quote > 0 && quote < equal { + equal = strings.index_byte(line, '=') + if equal < 0 { + continue + } + } else { + equal += 1 + } + + key = strings.trim_space(line[:equal]) + value = strings.trim_space(line[equal+1:]) + ok = true + return + } + + it.section = "" + return +} + +Map :: distinct map[string]map[string]string + +load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error) { + unquote :: proc(val: string) -> (string, runtime.Allocator_Error) { + if len(val) > 0 && (val[0] == '"' || val[0] == '\'') { + v, allocated, ok := strconv.unquote_string(val) + if !ok { + return strings.clone(val) + } + if allocated { + return v, nil + } + } + return strings.clone(val) + } + + context.allocator = allocator + + it := iterator_from_string(src, options) + + for key, value in iterate(&it) { + section := it.section + if section not_in m { + section = strings.clone(section) or_return + m[section] = {} + } + + // store key-value pair + pairs := &m[section] + new_key := unquote(key) or_return + if options.key_lower_case { + old_key := new_key + new_key = strings.to_lower(key) or_return + delete(old_key) or_return + } + pairs[new_key], err = unquote(value) or_return + } + return +} + +load_map_from_path :: proc(path: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error, ok: bool) { + data := os.read_entire_file(path, allocator) or_return + defer delete(data, allocator) + m, err = load_map_from_string(string(data), allocator, options) + ok = err == nil + defer if !ok { + delete_map(m) + } + return +} + +save_map_to_string :: proc(m: Map, allocator: runtime.Allocator) -> (data: string) { + b := strings.builder_make(allocator) + _, _ = write_map(strings.to_writer(&b), m) + return strings.to_string(b) +} + +delete_map :: proc(m: Map) { + allocator := m.allocator + for section, pairs in m { + for key, value in pairs { + delete(key, allocator) + delete(value, allocator) + } + delete(section) + delete(pairs) + } + delete(m) +} + +write_section :: proc(w: io.Writer, name: string, n_written: ^int = nil) -> (n: int, err: io.Error) { + defer if n_written != nil { n_written^ += n } + io.write_byte (w, '[', &n) or_return + io.write_string(w, name, &n) or_return + io.write_byte (w, ']', &n) or_return + return +} + +write_pair :: proc(w: io.Writer, key: string, value: $T, n_written: ^int = nil) -> (n: int, err: io.Error) { + defer if n_written != nil { n_written^ += n } + io.write_string(w, key, &n) or_return + io.write_string(w, " = ", &n) or_return + when intrinsics.type_is_string(T) { + val := string(value) + if len(val) > 0 && (val[0] == ' ' || val[len(val)-1] == ' ') { + io.write_quoted_string(w, val, n_written=&n) or_return + } else { + io.write_string(w, val, &n) or_return + } + } else { + n += fmt.wprint(w, value) + } + io.write_byte(w, '\n', &n) or_return + return +} + +write_map :: proc(w: io.Writer, m: Map) -> (n: int, err: io.Error) { + section_index := 0 + for section, pairs in m { + if section_index == 0 && section == "" { + // ignore section + } else { + write_section(w, section, &n) or_return + } + for key, value in pairs { + write_pair(w, key, value, &n) or_return + } + section_index += 1 + } + return +} diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 04ef6d434..009bf7ade 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -25,7 +25,7 @@ Marshal_Options :: struct { spec: Specification, // Use line breaks & tabs/spaces - pretty: bool, + pretty: bool, // Use spaces for indentation instead of tabs use_spaces: bool, @@ -34,7 +34,7 @@ Marshal_Options :: struct { spaces: int, // Output uint as hex in JSON5 & MJSON - write_uint_as_hex: bool, + write_uint_as_hex: bool, // If spec is MJSON and this is true, then keys will be quoted. // @@ -62,8 +62,8 @@ Marshal_Options :: struct { mjson_skipped_first_braces_end: bool, } -marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator) -> (data: []byte, err: Marshal_Error) { - b := strings.builder_make(allocator) +marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Marshal_Error) { + b := strings.builder_make(allocator, loc) defer if err != nil { strings.builder_destroy(&b) } @@ -100,45 +100,14 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case runtime.Type_Info_Integer: buf: [40]byte - u: u128 - switch i in a { - case i8: u = u128(i) - case i16: u = u128(i) - case i32: u = u128(i) - case i64: u = u128(i) - case i128: u = u128(i) - case int: u = u128(i) - case u8: u = u128(i) - case u16: u = u128(i) - case u32: u = u128(i) - case u64: u = u128(i) - case u128: u = u128(i) - case uint: u = u128(i) - case uintptr: u = u128(i) - - case i16le: u = u128(i) - case i32le: u = u128(i) - case i64le: u = u128(i) - case u16le: u = u128(i) - case u32le: u = u128(i) - case u64le: u = u128(i) - case u128le: u = u128(i) - - case i16be: u = u128(i) - case i32be: u = u128(i) - case i64be: u = u128(i) - case u16be: u = u128(i) - case u32be: u = u128(i) - case u64be: u = u128(i) - case u128be: u = u128(i) - } + u := cast_any_int_to_u128(a) s: string // allow uints to be printed as hex if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) { switch i in a { - case u8, u16, u32, u64, u128: + case u8, u16, u32, u64, u128: s = strconv.append_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix }) case: @@ -239,7 +208,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case runtime.Type_Info_Array: opt_write_start(w, opt, '[') or_return for i in 0.. (err: case runtime.Type_Info_Enumerated_Array: opt_write_start(w, opt, '[') or_return for i in 0.. (err: opt_write_start(w, opt, '[') or_return array := cast(^mem.Raw_Dynamic_Array)v.data for i in 0.. (err: opt_write_start(w, opt, '[') or_return slice := cast(^mem.Raw_Slice)v.data for i in 0.. (err: for bucket_index in 0.. (err: case cstring: name = string(s) } opt_write_key(w, opt, name) or_return - + case runtime.Type_Info_Integer: + buf: [40]byte + u := cast_any_int_to_u128(ka) + name = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil) + + opt_write_key(w, opt, name) or_return case: return .Unsupported_Type } } @@ -356,7 +330,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: slice.sort_by(sorted[:], proc(i, j: Entry) -> bool { return i.key < j.key }) for s, i in sorted { - opt_write_iteration(w, opt, i) or_return + opt_write_iteration(w, opt, i == 0) or_return opt_write_key(w, opt, s.key) or_return marshal_to_writer(w, s.value, opt) or_return } @@ -387,17 +361,17 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case runtime.Type_Info_Pointer, runtime.Type_Info_Multi_Pointer, runtime.Type_Info_Procedure: - return (^rawptr)(v.data)^ == nil + return (^rawptr)(v.data)^ == nil case runtime.Type_Info_Dynamic_Array: - return (^runtime.Raw_Dynamic_Array)(v.data).len == 0 + return (^runtime.Raw_Dynamic_Array)(v.data).len == 0 case runtime.Type_Info_Slice: - return (^runtime.Raw_Slice)(v.data).len == 0 + return (^runtime.Raw_Slice)(v.data).len == 0 case runtime.Type_Info_Union, runtime.Type_Info_Bit_Set, runtime.Type_Info_Soa_Pointer: return reflect.is_nil(v) case runtime.Type_Info_Map: - return (^runtime.Raw_Map)(v.data).len == 0 + return (^runtime.Raw_Map)(v.data).len == 0 } return false } @@ -405,10 +379,16 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: marshal_struct_fields :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: Marshal_Error) { ti := runtime.type_info_base(type_info_of(v.id)) info := ti.variant.(runtime.Type_Info_Struct) - for name, i in info.names { + first_iteration := true + for name, i in info.names[:info.field_count] { omitempty := false json_name, extra := json_name_from_tag_value(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "json")) + + if json_name == "-" { + continue + } + for flag in strings.split_iterator(&extra, ",") { switch flag { case "omitempty": @@ -420,11 +400,12 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: data := rawptr(uintptr(v.data) + info.offsets[i]) the_value := any{data, id} - if is_omitempty(the_value) { + if omitempty && is_omitempty(the_value) { continue } - opt_write_iteration(w, opt, i) or_return + opt_write_iteration(w, opt, first_iteration) or_return + first_iteration = false if json_name != "" { opt_write_key(w, opt, json_name) or_return } else { @@ -469,12 +450,15 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case: panic("Invalid union tag type") } - if v.data == nil || tag == 0 { - io.write_string(w, "null") or_return - } else { - id := info.variants[tag-1].id - return marshal_to_writer(w, any{v.data, id}, opt) + if !info.no_nil { + if tag == 0 { + io.write_string(w, "null") or_return + return nil + } + tag -= 1 } + id := info.variants[tag].id + return marshal_to_writer(w, any{v.data, id}, opt) case runtime.Type_Info_Enum: if !opt.use_enum_names || len(info.names) == 0 { @@ -536,8 +520,6 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case: panic("unknown bit_size size") } io.write_u64(w, bit_data) or_return - - return .Unsupported_Type } return @@ -587,10 +569,10 @@ opt_write_start :: proc(w: io.Writer, opt: ^Marshal_Options, c: byte) -> (err: i } // insert comma separation and write indentations -opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, iteration: int) -> (err: io.Error) { +opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, first_iteration: bool) -> (err: io.Error) { switch opt.spec { - case .JSON, .JSON5: - if iteration > 0 { + case .JSON, .JSON5: + if !first_iteration { io.write_byte(w, ',') or_return if opt.pretty { @@ -600,8 +582,8 @@ opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, iteration: int) opt_write_indentation(w, opt) or_return - case .MJSON: - if iteration > 0 { + case .MJSON: + if !first_iteration { // on pretty no commas necessary if opt.pretty { io.write_byte(w, '\n') or_return @@ -654,3 +636,41 @@ opt_write_indentation :: proc(w: io.Writer, opt: ^Marshal_Options) -> (err: io.E return } + +@(private) +cast_any_int_to_u128 :: proc(any_int_value: any) -> u128 { + u: u128 = 0 + switch i in any_int_value { + case i8: u = u128(i) + case i16: u = u128(i) + case i32: u = u128(i) + case i64: u = u128(i) + case i128: u = u128(i) + case int: u = u128(i) + case u8: u = u128(i) + case u16: u = u128(i) + case u32: u = u128(i) + case u64: u = u128(i) + case u128: u = u128(i) + case uint: u = u128(i) + case uintptr: u = u128(i) + + case i16le: u = u128(i) + case i32le: u = u128(i) + case i64le: u = u128(i) + case u16le: u = u128(i) + case u32le: u = u128(i) + case u64le: u = u128(i) + case u128le: u = u128(i) + + case i16be: u = u128(i) + case i32be: u = u128(i) + case i64be: u = u128(i) + case u16be: u = u128(i) + case u32be: u = u128(i) + case u64be: u = u128(i) + case u128be: u = u128(i) + } + + return u +} \ No newline at end of file diff --git a/core/encoding/json/parser.odin b/core/encoding/json/parser.odin index 3973725dc..38f71edf6 100644 --- a/core/encoding/json/parser.odin +++ b/core/encoding/json/parser.odin @@ -28,27 +28,27 @@ make_parser_from_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, par } -parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> (Value, Error) { - return parse_string(string(data), spec, parse_integers, allocator) +parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) { + return parse_string(string(data), spec, parse_integers, allocator, loc) } -parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> (Value, Error) { +parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) { context.allocator = allocator p := make_parser_from_string(data, spec, parse_integers, allocator) switch p.spec { case .JSON: - return parse_object(&p) + return parse_object(&p, loc) case .JSON5: - return parse_value(&p) + return parse_value(&p, loc) case .SJSON: #partial switch p.curr_token.kind { case .Ident, .String: - return parse_object_body(&p, .EOF) + return parse_object_body(&p, .EOF, loc) } - return parse_value(&p) + return parse_value(&p, loc) } - return parse_object(&p) + return parse_object(&p, loc) } token_end_pos :: proc(tok: Token) -> Pos { @@ -106,7 +106,7 @@ parse_comma :: proc(p: ^Parser) -> (do_break: bool) { return false } -parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) { +parse_value :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) { err = .None token := p.curr_token #partial switch token.kind { @@ -142,13 +142,13 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) { case .String: advance_token(p) - return unquote_string(token, p.spec, p.allocator) + return unquote_string(token, p.spec, p.allocator, loc) case .Open_Brace: - return parse_object(p) + return parse_object(p, loc) case .Open_Bracket: - return parse_array(p) + return parse_array(p, loc) case: if p.spec != .JSON { @@ -176,7 +176,7 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) { return } -parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) { +parse_array :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) { err = .None expect_token(p, .Open_Bracket) or_return @@ -184,14 +184,14 @@ parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) { array.allocator = p.allocator defer if err != nil { for elem in array { - destroy_value(elem) + destroy_value(elem, loc=loc) } - delete(array) + delete(array, loc) } for p.curr_token.kind != .Close_Bracket { - elem := parse_value(p) or_return - append(&array, elem) + elem := parse_value(p, loc) or_return + append(&array, elem, loc) if parse_comma(p) { break @@ -228,38 +228,39 @@ clone_string :: proc(s: string, allocator: mem.Allocator, loc := #caller_locatio return } -parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator) -> (key: string, err: Error) { +parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator, loc := #caller_location) -> (key: string, err: Error) { tok := p.curr_token if p.spec != .JSON { if allow_token(p, .Ident) { - return clone_string(tok.text, key_allocator) + return clone_string(tok.text, key_allocator, loc) } } if tok_err := expect_token(p, .String); tok_err != nil { err = .Expected_String_For_Object_Key return } - return unquote_string(tok, p.spec, key_allocator) + return unquote_string(tok, p.spec, key_allocator, loc) } -parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, err: Error) { - obj.allocator = p.allocator +parse_object_body :: proc(p: ^Parser, end_token: Token_Kind, loc := #caller_location) -> (obj: Object, err: Error) { + obj = make(Object, allocator=p.allocator, loc=loc) + defer if err != nil { for key, elem in obj { - delete(key, p.allocator) - destroy_value(elem) + delete(key, p.allocator, loc) + destroy_value(elem, loc=loc) } - delete(obj) + delete(obj, loc) } for p.curr_token.kind != end_token { - key := parse_object_key(p, p.allocator) or_return + key := parse_object_key(p, p.allocator, loc) or_return parse_colon(p) or_return - elem := parse_value(p) or_return + elem := parse_value(p, loc) or_return if key in obj { err = .Duplicate_Object_Key - delete(key, p.allocator) + delete(key, p.allocator, loc) return } @@ -267,7 +268,7 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, er // inserting empty key/values into the object and for those we do not // want to allocate anything if key != "" { - reserve_error := reserve(&obj, len(obj) + 1) + reserve_error := reserve(&obj, len(obj) + 1, loc) if reserve_error == mem.Allocator_Error.Out_Of_Memory { return nil, .Out_Of_Memory } @@ -281,9 +282,9 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, er return obj, .None } -parse_object :: proc(p: ^Parser) -> (value: Value, err: Error) { +parse_object :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) { expect_token(p, .Open_Brace) or_return - obj := parse_object_body(p, .Close_Brace) or_return + obj := parse_object_body(p, .Close_Brace, loc) or_return expect_token(p, .Close_Brace) or_return return obj, .None } @@ -480,4 +481,4 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a } return string(b[:w]), nil -} +} \ No newline at end of file diff --git a/core/encoding/json/types.odin b/core/encoding/json/types.odin index 73e183615..41eb21377 100644 --- a/core/encoding/json/types.odin +++ b/core/encoding/json/types.odin @@ -89,22 +89,22 @@ Error :: enum { -destroy_value :: proc(value: Value, allocator := context.allocator) { +destroy_value :: proc(value: Value, allocator := context.allocator, loc := #caller_location) { context.allocator = allocator #partial switch v in value { case Object: for key, elem in v { - delete(key) - destroy_value(elem) + delete(key, loc=loc) + destroy_value(elem, loc=loc) } - delete(v) + delete(v, loc=loc) case Array: for elem in v { - destroy_value(elem) + destroy_value(elem, loc=loc) } - delete(v) + delete(v, loc=loc) case String: - delete(v) + delete(v, loc=loc) } } diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index 691303521..1c1801bcd 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -348,7 +348,7 @@ json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) { json_name = value if comma_index := strings.index_byte(json_name, ','); comma_index >= 0 { json_name = json_name[:comma_index] - extra = json_name[comma_index:] + extra = value[1 + comma_index:] } return } @@ -368,7 +368,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm #partial switch t in ti.variant { case reflect.Type_Info_Struct: - if t.is_raw_union { + if .raw_union in t.flags { return UNSUPPORTED_TYPE } @@ -475,7 +475,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } case reflect.Type_Info_Map: - if !reflect.is_string(t.key) { + if !reflect.is_string(t.key) && !reflect.is_integer(t.key) { return UNSUPPORTED_TYPE } raw_map := (^mem.Raw_Map)(v.data) @@ -492,25 +492,39 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm key, _ := parse_object_key(p, p.allocator) unmarshal_expect_token(p, .Colon) - + mem.zero_slice(elem_backing) if uerr := unmarshal_value(p, map_backing_value); uerr != nil { delete(key, p.allocator) return uerr } - key_ptr := rawptr(&key) + key_ptr: rawptr - key_cstr: cstring - if reflect.is_cstring(t.key) { - key_cstr = cstring(raw_data(key)) - key_ptr = &key_cstr + #partial switch tk in t.key.variant { + case runtime.Type_Info_String: + key_ptr = rawptr(&key) + key_cstr: cstring + if reflect.is_cstring(t.key) { + key_cstr = cstring(raw_data(key)) + key_ptr = &key_cstr + } + case runtime.Type_Info_Integer: + i, ok := strconv.parse_i128(key) + if !ok { return UNSUPPORTED_TYPE } + key_ptr = rawptr(&i) + case: return UNSUPPORTED_TYPE } - + set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data) if set_ptr == nil { delete(key, p.allocator) } + + // there's no need to keep string value on the heap, since it was copied into map + if reflect.is_integer(t.key) { + delete(key, p.allocator) + } if parse_comma(p) { break map_loop diff --git a/core/encoding/uuid/LICENSE b/core/encoding/uuid/LICENSE new file mode 100644 index 000000000..e4e21e62d --- /dev/null +++ b/core/encoding/uuid/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2024, Feoramund + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/core/encoding/uuid/definitions.odin b/core/encoding/uuid/definitions.odin new file mode 100644 index 000000000..fe13ca99a --- /dev/null +++ b/core/encoding/uuid/definitions.odin @@ -0,0 +1,67 @@ +package uuid + +// A RFC 4122 Universally Unique Identifier +Identifier :: distinct [16]u8 + +EXPECTED_LENGTH :: 8 + 4 + 4 + 4 + 12 + 4 + +VERSION_BYTE_INDEX :: 6 +VARIANT_BYTE_INDEX :: 8 + +// The number of 100-nanosecond intervals between 1582-10-15 and 1970-01-01. +HNS_INTERVALS_BETWEEN_GREG_AND_UNIX :: 141427 * 24 * 60 * 60 * 1000 * 1000 * 10 + +VERSION_7_TIME_MASK :: 0xffffffff_ffff0000_00000000_00000000 +VERSION_7_TIME_SHIFT :: 80 +VERSION_7_COUNTER_MASK :: 0x00000000_00000fff_00000000_00000000 +VERSION_7_COUNTER_SHIFT :: 64 + +@(private) +NO_CSPRNG_ERROR :: "The context random generator is not cryptographic. See the documentation for an example of how to set one up." +@(private) +BIG_CLOCK_ERROR :: "The clock sequence can only hold 14 bits of data, therefore no number greater than 16,383 (0x3FFF)." +@(private) +VERSION_7_BIG_COUNTER_ERROR :: "This implementation of the version 7 UUID counter can only hold 12 bits of data, therefore no number greater than 4,095 (0xFFF)." + +Read_Error :: enum { + None, + Invalid_Length, + Invalid_Hexadecimal, + Invalid_Separator, +} + +Variant_Type :: enum { + Unknown, + Reserved_Apollo_NCS, // 0b0xx + RFC_4122, // 0b10x + Reserved_Microsoft_COM, // 0b110 + Reserved_Future, // 0b111 +} + +// Name string is a fully-qualified domain name. +@(rodata) +Namespace_DNS := Identifier { + 0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, + 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8, +} + +// Name string is a URL. +@(rodata) +Namespace_URL := Identifier { + 0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, + 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8, +} + +// Name string is an ISO OID. +@(rodata) +Namespace_OID := Identifier { + 0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, + 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8, +} + +// Name string is an X.500 DN (in DER or a text output format). +@(rodata) +Namespace_X500 := Identifier { + 0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, + 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8, +} diff --git a/core/encoding/uuid/doc.odin b/core/encoding/uuid/doc.odin new file mode 100644 index 000000000..6fa375b72 --- /dev/null +++ b/core/encoding/uuid/doc.odin @@ -0,0 +1,46 @@ +/* +package uuid implements Universally Unique Identifiers according to the +standard originally outlined in RFC 4122 with additions from RFC 9562. + +The UUIDs are textually represented and read in the following string format: +`00000000-0000-v000-V000-000000000000` + +`v` is where the version bits reside, and `V` is where the variant bits reside. +The meaning of the other bits is version-dependent. + +Outside of string representations, UUIDs are represented in memory by a 128-bit +structure organized as an array of 16 bytes. + + +Of the UUID versions which may make use of random number generation, a +requirement is placed upon them that the underlying generator be +cryptographically-secure, per RFC 9562's suggestion. + +- Version 1 without a node argument. +- Version 4 in all cases. +- Version 6 without either a clock or node argument. +- Version 7 in all cases. + +Here's an example of how to set up one: + + import "core:crypto" + import "core:encoding/uuid" + + main :: proc() { + my_uuid: uuid.Identifier + + { + // This scope will have a CSPRNG. + context.random_generator = crypto.random_generator() + my_uuid = uuid.generate_v7() + } + + // Back to the default random number generator. + } + + +For more information on the specifications, see here: +- https://www.rfc-editor.org/rfc/rfc4122.html +- https://www.rfc-editor.org/rfc/rfc9562.html +*/ +package uuid diff --git a/core/encoding/uuid/generation.odin b/core/encoding/uuid/generation.odin new file mode 100644 index 000000000..7c9d4b80c --- /dev/null +++ b/core/encoding/uuid/generation.odin @@ -0,0 +1,333 @@ +package uuid + +import "base:runtime" +import "core:crypto/hash" +import "core:math/rand" +import "core:time" + +/* +Generate a version 1 UUID. + +Inputs: +- clock_seq: The clock sequence, a number which must be initialized to a random number once in the lifetime of a system. +- node: An optional 48-bit spatially unique identifier, specified to be the IEEE 802 address of the system. + If one is not provided or available, 48 bits of random state will take its place. +- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time. + +Returns: +- result: The generated UUID. +*/ +generate_v1 :: proc(clock_seq: u16, node: Maybe([6]u8) = nil, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) { + assert(clock_seq <= 0x3FFF, BIG_CLOCK_ERROR) + unix_time_in_hns_intervals := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 100 + + uuid_timestamp := cast(u64le)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals) + uuid_timestamp_octets := transmute([8]u8)uuid_timestamp + + result[0] = uuid_timestamp_octets[0] + result[1] = uuid_timestamp_octets[1] + result[2] = uuid_timestamp_octets[2] + result[3] = uuid_timestamp_octets[3] + result[4] = uuid_timestamp_octets[4] + result[5] = uuid_timestamp_octets[5] + + result[6] = uuid_timestamp_octets[6] >> 4 + result[7] = uuid_timestamp_octets[6] << 4 | uuid_timestamp_octets[7] + + if realized_node, ok := node.?; ok { + mutable_node := realized_node + runtime.mem_copy_non_overlapping(&result[10], &mutable_node[0], 6) + } else { + assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR) + bytes_generated := rand.read(result[10:]) + assert(bytes_generated == 6, "RNG failed to generate 6 bytes for UUID v1.") + } + + result[VERSION_BYTE_INDEX] |= 0x10 + result[VARIANT_BYTE_INDEX] |= 0x80 + + result[8] |= cast(u8)(clock_seq & 0x3F00 >> 8) + result[9] = cast(u8)clock_seq + + return +} + +/* +Generate a version 4 UUID. + +This UUID will be pseudorandom, save for 6 pre-determined version and variant bits. + +Returns: +- result: The generated UUID. +*/ +generate_v4 :: proc() -> (result: Identifier) { + assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR) + bytes_generated := rand.read(result[:]) + assert(bytes_generated == 16, "RNG failed to generate 16 bytes for UUID v4.") + + result[VERSION_BYTE_INDEX] &= 0x0F + result[VERSION_BYTE_INDEX] |= 0x40 + + result[VARIANT_BYTE_INDEX] &= 0x3F + result[VARIANT_BYTE_INDEX] |= 0x80 + + return +} + +/* +Generate a version 6 UUID. + +Inputs: +- clock_seq: The clock sequence from version 1, now made optional. + If unspecified, it will be replaced with random bits. +- node: An optional 48-bit spatially unique identifier, specified to be the IEEE 802 address of the system. + If one is not provided or available, 48 bits of random state will take its place. +- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time. + +Returns: +- result: The generated UUID. +*/ +generate_v6 :: proc(clock_seq: Maybe(u16) = nil, node: Maybe([6]u8) = nil, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) { + unix_time_in_hns_intervals := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 100 + + uuid_timestamp := cast(u128be)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals) + + result = transmute(Identifier)( + uuid_timestamp & 0x0FFFFFFF_FFFFF000 << 68 | + uuid_timestamp & 0x00000000_00000FFF << 64 + ) + + if realized_clock_seq, ok := clock_seq.?; ok { + assert(realized_clock_seq <= 0x3FFF, BIG_CLOCK_ERROR) + result[8] |= cast(u8)(realized_clock_seq & 0x3F00 >> 8) + result[9] = cast(u8)realized_clock_seq + } else { + assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR) + temporary: [2]u8 + bytes_generated := rand.read(temporary[:]) + assert(bytes_generated == 2, "RNG failed to generate 2 bytes for UUID v1.") + result[8] |= temporary[0] & 0x3F + result[9] = temporary[1] + } + + if realized_node, ok := node.?; ok { + mutable_node := realized_node + runtime.mem_copy_non_overlapping(&result[10], &mutable_node[0], 6) + } else { + assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR) + bytes_generated := rand.read(result[10:]) + assert(bytes_generated == 6, "RNG failed to generate 6 bytes for UUID v1.") + } + + result[VERSION_BYTE_INDEX] |= 0x60 + result[VARIANT_BYTE_INDEX] |= 0x80 + + return +} + +/* +Generate a version 7 UUID. + +This UUID will be pseudorandom, save for 6 pre-determined version and variant +bits and a 48-bit timestamp. + +It is designed with time-based sorting in mind, such as for database usage, as +the highest bits are allocated from the timestamp of when it is created. + +Inputs: +- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time. + +Returns: +- result: The generated UUID. +*/ +generate_v7_basic :: proc(timestamp: Maybe(time.Time) = nil) -> (result: Identifier) { + assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR) + unix_time_in_milliseconds := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 1e6 + + result = transmute(Identifier)(cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT) + + bytes_generated := rand.read(result[6:]) + assert(bytes_generated == 10, "RNG failed to generate 10 bytes for UUID v7.") + + result[VERSION_BYTE_INDEX] &= 0x0F + result[VERSION_BYTE_INDEX] |= 0x70 + + result[VARIANT_BYTE_INDEX] &= 0x3F + result[VARIANT_BYTE_INDEX] |= 0x80 + + return +} + +/* +Generate a version 7 UUID that has an incremented counter. + +This UUID will be pseudorandom, save for 6 pre-determined version and variant +bits, a 48-bit timestamp, and 12 bits of counter state. + +It is designed with time-based sorting in mind, such as for database usage, as +the highest bits are allocated from the timestamp of when it is created. + +This procedure is preferable if you are generating hundreds or thousands of +UUIDs as a batch within the span of a millisecond. Do note that the counter +only has 12 bits of state, thus `counter` cannot exceed the number 4,095. + +Example: + + import "core:uuid" + + // Create a batch of UUIDs all at once. + batch: [dynamic]uuid.Identifier + + for i: u16 = 0; i < 1000; i += 1 { + my_uuid := uuid.generate_v7_counter(i) + append(&batch, my_uuid) + } + +Inputs: +- counter: A 12-bit value which should be incremented each time a UUID is generated in a batch. +- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time. + +Returns: +- result: The generated UUID. +*/ +generate_v7_with_counter :: proc(counter: u16, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) { + assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR) + assert(counter <= 0x0fff, VERSION_7_BIG_COUNTER_ERROR) + unix_time_in_milliseconds := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 1e6 + + result = transmute(Identifier)( + cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT | + cast(u128be)counter << VERSION_7_COUNTER_SHIFT + ) + + bytes_generated := rand.read(result[8:]) + assert(bytes_generated == 8, "RNG failed to generate 8 bytes for UUID v7.") + + result[VERSION_BYTE_INDEX] &= 0x0F + result[VERSION_BYTE_INDEX] |= 0x70 + + result[VARIANT_BYTE_INDEX] &= 0x3F + result[VARIANT_BYTE_INDEX] |= 0x80 + + return +} + +generate_v7 :: proc { + generate_v7_basic, + generate_v7_with_counter, +} + +/* +Generate a version 8 UUID using a specific hashing algorithm. + +This UUID is generated by hashing a name with a namespace. + +Note that all version 8 UUIDs are for experimental or vendor-specific use +cases, per the specification. This use case in particular is for offering a +non-legacy alternative to UUID versions 3 and 5. + +Inputs: +- namespace: An `Identifier` that is used to represent the underlying namespace. + This can be any one of the `Namespace_*` values provided in this package. +- name: The byte slice which will be hashed with the namespace. +- algorithm: A hashing algorithm from `core:crypto/hash`. + +Returns: +- result: The generated UUID. + +Example: + import "core:crypto/hash" + import "core:encoding/uuid" + import "core:fmt" + + main :: proc() { + my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256) + my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator) + fmt.println(my_uuid_string) + } + +Output: + + 3730f688-4bff-8dce-9cbf-74a3960c5703 + +*/ +generate_v8_hash_bytes :: proc( + namespace: Identifier, + name: []byte, + algorithm: hash.Algorithm, +) -> ( + result: Identifier, +) { + // 128 bytes should be enough for the foreseeable future. + digest: [128]byte + + assert(hash.DIGEST_SIZES[algorithm] >= 16, "Per RFC 9562, the hashing algorithm used must generate a digest of 128 bits or larger.") + assert(hash.DIGEST_SIZES[algorithm] < len(digest), "Digest size is too small for this algorithm. The buffer must be increased.") + + hash_context: hash.Context + hash.init(&hash_context, algorithm) + + mutable_namespace := namespace + hash.update(&hash_context, mutable_namespace[:]) + hash.update(&hash_context, name[:]) + hash.final(&hash_context, digest[:]) + + runtime.mem_copy_non_overlapping(&result, &digest, 16) + + result[VERSION_BYTE_INDEX] &= 0x0F + result[VERSION_BYTE_INDEX] |= 0x80 + + result[VARIANT_BYTE_INDEX] &= 0x3F + result[VARIANT_BYTE_INDEX] |= 0x80 + + return +} + +/* +Generate a version 8 UUID using a specific hashing algorithm. + +This UUID is generated by hashing a name with a namespace. + +Note that all version 8 UUIDs are for experimental or vendor-specific use +cases, per the specification. This use case in particular is for offering a +non-legacy alternative to UUID versions 3 and 5. + +Inputs: +- namespace: An `Identifier` that is used to represent the underlying namespace. + This can be any one of the `Namespace_*` values provided in this package. +- name: The string which will be hashed with the namespace. +- algorithm: A hashing algorithm from `core:crypto/hash`. + +Returns: +- result: The generated UUID. + +Example: + import "core:crypto/hash" + import "core:encoding/uuid" + import "core:fmt" + + main :: proc() { + my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256) + my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator) + fmt.println(my_uuid_string) + } + +Output: + + 3730f688-4bff-8dce-9cbf-74a3960c5703 + +*/ +generate_v8_hash_string :: proc( + namespace: Identifier, + name: string, + algorithm: hash.Algorithm, +) -> ( + result: Identifier, +) { + return generate_v8_hash_bytes(namespace, transmute([]byte)name, algorithm) +} + +generate_v8_hash :: proc { + generate_v8_hash_bytes, + generate_v8_hash_string, +} diff --git a/core/encoding/uuid/legacy/legacy.odin b/core/encoding/uuid/legacy/legacy.odin new file mode 100644 index 000000000..d5f3df617 --- /dev/null +++ b/core/encoding/uuid/legacy/legacy.odin @@ -0,0 +1,146 @@ +/* +package uuid/legacy implements versions 3 and 5 of UUID generation, both of +which are using hashing algorithms (MD5 and SHA1, respectively) that are known +these days to no longer be secure. +*/ +package uuid_legacy + +import "base:runtime" +import "core:crypto/legacy/md5" +import "core:crypto/legacy/sha1" +import "core:encoding/uuid" + +Identifier :: uuid.Identifier +VERSION_BYTE_INDEX :: uuid.VERSION_BYTE_INDEX +VARIANT_BYTE_INDEX :: uuid.VARIANT_BYTE_INDEX + + +/* +Generate a version 3 UUID. + +This UUID is generated with a MD5 hash of a name and a namespace. + +Inputs: +- namespace: An `Identifier` that is used to represent the underlying namespace. + This can be any one of the `Namespace_*` values provided in the `uuid` package. +- name: The byte slice which will be hashed with the namespace. + +Returns: +- result: The generated UUID. +*/ +generate_v3_bytes :: proc( + namespace: Identifier, + name: []byte, +) -> ( + result: Identifier, +) { + namespace := namespace + + ctx: md5.Context + md5.init(&ctx) + md5.update(&ctx, namespace[:]) + md5.update(&ctx, name) + md5.final(&ctx, result[:]) + + result[VERSION_BYTE_INDEX] &= 0x0F + result[VERSION_BYTE_INDEX] |= 0x30 + + result[VARIANT_BYTE_INDEX] &= 0x3F + result[VARIANT_BYTE_INDEX] |= 0x80 + + return +} + +/* +Generate a version 3 UUID. + +This UUID is generated with a MD5 hash of a name and a namespace. + +Inputs: +- namespace: An `Identifier` that is used to represent the underlying namespace. + This can be any one of the `Namespace_*` values provided in the `uuid` package. +- name: The string which will be hashed with the namespace. + +Returns: +- result: The generated UUID. +*/ +generate_v3_string :: proc( + namespace: Identifier, + name: string, +) -> ( + result: Identifier, +) { + return generate_v3_bytes(namespace, transmute([]byte)name) +} + +generate_v3 :: proc { + generate_v3_bytes, + generate_v3_string, +} + +/* +Generate a version 5 UUID. + +This UUID is generated with a SHA1 hash of a name and a namespace. + +Inputs: +- namespace: An `Identifier` that is used to represent the underlying namespace. + This can be any one of the `Namespace_*` values provided in the `uuid` package. +- name: The byte slice which will be hashed with the namespace. + +Returns: +- result: The generated UUID. +*/ +generate_v5_bytes :: proc( + namespace: Identifier, + name: []byte, +) -> ( + result: Identifier, +) { + namespace := namespace + digest: [sha1.DIGEST_SIZE]byte + + ctx: sha1.Context + sha1.init(&ctx) + sha1.update(&ctx, namespace[:]) + sha1.update(&ctx, name) + sha1.final(&ctx, digest[:]) + + runtime.mem_copy_non_overlapping(&result, &digest, 16) + + result[VERSION_BYTE_INDEX] &= 0x0F + result[VERSION_BYTE_INDEX] |= 0x50 + + result[VARIANT_BYTE_INDEX] &= 0x3F + result[VARIANT_BYTE_INDEX] |= 0x80 + + return +} + +/* +Generate a version 5 UUID. + +This UUID is generated with a SHA1 hash of a name and a namespace. + +Inputs: +- namespace: An `Identifier` that is used to represent the underlying namespace. + This can be any one of the `Namespace_*` values provided in the `uuid` package. +- name: The string which will be hashed with the namespace. + +Returns: +- result: The generated UUID. +*/ +generate_v5_string :: proc( + namespace: Identifier, + name: string, +) -> ( + result: Identifier, +) { + return generate_v5_bytes(namespace, transmute([]byte)name) +} + +generate_v5 :: proc { + generate_v5_bytes, + generate_v5_string, +} + diff --git a/core/encoding/uuid/reading.odin b/core/encoding/uuid/reading.odin new file mode 100644 index 000000000..c91c82465 --- /dev/null +++ b/core/encoding/uuid/reading.odin @@ -0,0 +1,242 @@ +package uuid + +import "base:runtime" +import "core:time" + +/* +Convert a string to a UUID. + +Inputs: +- str: A string in the 8-4-4-4-12 format. + +Returns: +- id: The converted identifier, or `nil` if there is an error. +- error: A description of the error, or `nil` if successful. +*/ +read :: proc "contextless" (str: string) -> (id: Identifier, error: Read_Error) #no_bounds_check { + // Only exact-length strings are acceptable. + if len(str) != EXPECTED_LENGTH { + return {}, .Invalid_Length + } + + // Check ahead to see if the separators are in the right places. + if str[8] != '-' || str[13] != '-' || str[18] != '-' || str[23] != '-' { + return {}, .Invalid_Separator + } + + read_nibble :: proc "contextless" (nibble: u8) -> u8 { + switch nibble { + case '0' ..= '9': + return nibble - '0' + case 'A' ..= 'F': + return nibble - 'A' + 10 + case 'a' ..= 'f': + return nibble - 'a' + 10 + case: + // Return an error value. + return 0xFF + } + } + + index := 0 + octet_index := 0 + + CHUNKS :: [5]int{8, 4, 4, 4, 12} + + for chunk in CHUNKS { + for i := index; i < index + chunk; i += 2 { + high := read_nibble(str[i]) + low := read_nibble(str[i + 1]) + + if high | low > 0xF { + return {}, .Invalid_Hexadecimal + } + + id[octet_index] = low | high << 4 + octet_index += 1 + } + + index += chunk + 1 + } + + return +} + +/* +Get the version of a UUID. + +Inputs: +- id: The identifier. + +Returns: +- number: The version number. +*/ +version :: proc "contextless" (id: Identifier) -> (number: int) #no_bounds_check { + return cast(int)(id[VERSION_BYTE_INDEX] & 0xF0 >> 4) +} + +/* +Get the variant of a UUID. + +Inputs: +- id: The identifier. + +Returns: +- variant: The variant type. +*/ +variant :: proc "contextless" (id: Identifier) -> (variant: Variant_Type) #no_bounds_check { + switch { + case id[VARIANT_BYTE_INDEX] & 0x80 == 0: + return .Reserved_Apollo_NCS + case id[VARIANT_BYTE_INDEX] & 0xC0 == 0x80: + return .RFC_4122 + case id[VARIANT_BYTE_INDEX] & 0xE0 == 0xC0: + return .Reserved_Microsoft_COM + case id[VARIANT_BYTE_INDEX] & 0xF0 == 0xE0: + return .Reserved_Future + case: + return .Unknown + } +} + +/* +Get the clock sequence of a version 1 or version 6 UUID. + +Inputs: +- id: The identifier. + +Returns: +- clock_seq: The 14-bit clock sequence field. +*/ +clock_seq :: proc "contextless" (id: Identifier) -> (clock_seq: u16) { + return cast(u16)id[9] | cast(u16)id[8] & 0x3F << 8 +} + +/* +Get the node of a version 1 or version 6 UUID. + +Inputs: +- id: The identifier. + +Returns: +- node: The 48-bit spatially unique identifier. +*/ +node :: proc "contextless" (id: Identifier) -> (node: [6]u8) { + mutable_id := id + runtime.mem_copy_non_overlapping(&node, &mutable_id[10], 6) + return +} + +/* +Get the raw timestamp of a version 1 UUID. + +Inputs: +- id: The identifier. + +Returns: +- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15. +*/ +raw_time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: u64) { + timestamp_octets: [8]u8 + + timestamp_octets[0] = id[0] + timestamp_octets[1] = id[1] + timestamp_octets[2] = id[2] + timestamp_octets[3] = id[3] + timestamp_octets[4] = id[4] + timestamp_octets[5] = id[5] + + timestamp_octets[6] = id[6] << 4 | id[7] >> 4 + timestamp_octets[7] = id[7] & 0xF + + return cast(u64)transmute(u64le)timestamp_octets +} + + +/* +Get the timestamp of a version 1 UUID. + +Inputs: +- id: The identifier. + +Returns: +- timestamp: The timestamp of the UUID. +*/ +time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) { + return time.from_nanoseconds(cast(i64)(raw_time_v1(id) - HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100) +} + +/* +Get the raw timestamp of a version 6 UUID. + +Inputs: +- id: The identifier. + +Returns: +- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15. +*/ +raw_time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: u64) { + temporary := transmute(u128be)id + + timestamp |= cast(u64)(temporary & 0xFFFFFFFF_FFFF0000_00000000_00000000 >> 68) + timestamp |= cast(u64)(temporary & 0x00000000_00000FFF_00000000_00000000 >> 64) + + return timestamp +} + +/* +Get the timestamp of a version 6 UUID. + +Inputs: +- id: The identifier. + +Returns: +- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15. +*/ +time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) { + return time.from_nanoseconds(cast(i64)(raw_time_v6(id) - HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100) +} + +/* +Get the raw timestamp of a version 7 UUID. + +Inputs: +- id: The identifier. + +Returns: +- timestamp: The timestamp, in milliseconds since the UNIX epoch. +*/ +raw_time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: u64) { + time_bits := transmute(u128be)id & VERSION_7_TIME_MASK + return cast(u64)(time_bits >> VERSION_7_TIME_SHIFT) +} + +/* +Get the timestamp of a version 7 UUID. + +Inputs: +- id: The identifier. + +Returns: +- timestamp: The timestamp, in milliseconds since the UNIX epoch. +*/ +time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) { + return time.from_nanoseconds(cast(i64)raw_time_v7(id) * 1e6) +} + +/* +Get the 12-bit counter value of a version 7 UUID. + +The UUID must have been generated with a counter, otherwise this procedure will +return random bits. + +Inputs: +- id: The identifier. + +Returns: +- counter: The 12-bit counter value. +*/ +counter_v7 :: proc "contextless" (id: Identifier) -> (counter: u16) { + counter_bits := transmute(u128be)id & VERSION_7_COUNTER_MASK + return cast(u16)(counter_bits >> VERSION_7_COUNTER_SHIFT) +} diff --git a/core/encoding/uuid/stamping.odin b/core/encoding/uuid/stamping.odin new file mode 100644 index 000000000..3ca61fe44 --- /dev/null +++ b/core/encoding/uuid/stamping.odin @@ -0,0 +1,89 @@ +package uuid + +import "base:runtime" + +/* +Stamp a 128-bit integer as being a valid version 8 UUID. + +Per the specification, all version 8 UUIDs are either for experimental or +vendor-specific purposes. This procedure allows for converting arbitrary data +into custom UUIDs. + +Inputs: +- integer: Any integer type. + +Returns: +- result: A valid version 8 UUID. +*/ +stamp_v8_int :: proc(#any_int integer: u128) -> (result: Identifier) { + result = transmute(Identifier)cast(u128be)integer + + result[VERSION_BYTE_INDEX] &= 0x0F + result[VERSION_BYTE_INDEX] |= 0x80 + + result[VARIANT_BYTE_INDEX] &= 0x3F + result[VARIANT_BYTE_INDEX] |= 0x80 + + return +} + +/* +Stamp an array of 16 bytes as being a valid version 8 UUID. + +Per the specification, all version 8 UUIDs are either for experimental or +vendor-specific purposes. This procedure allows for converting arbitrary data +into custom UUIDs. + +Inputs: +- array: An array of 16 bytes. + +Returns: +- result: A valid version 8 UUID. +*/ +stamp_v8_array :: proc(array: [16]u8) -> (result: Identifier) { + result = Identifier(array) + + result[VERSION_BYTE_INDEX] &= 0x0F + result[VERSION_BYTE_INDEX] |= 0x80 + + result[VARIANT_BYTE_INDEX] &= 0x3F + result[VARIANT_BYTE_INDEX] |= 0x80 + + return +} + +/* +Stamp a slice of bytes as being a valid version 8 UUID. + +If the slice is less than 16 bytes long, the data available will be used. +If it is longer than 16 bytes, only the first 16 will be used. + +This procedure does not modify the underlying slice. + +Per the specification, all version 8 UUIDs are either for experimental or +vendor-specific purposes. This procedure allows for converting arbitrary data +into custom UUIDs. + +Inputs: +- slice: A slice of bytes. + +Returns: +- result: A valid version 8 UUID. +*/ +stamp_v8_slice :: proc(slice: []u8) -> (result: Identifier) { + runtime.mem_copy_non_overlapping(&result, &slice[0], min(16, len(slice))) + + result[VERSION_BYTE_INDEX] &= 0x0F + result[VERSION_BYTE_INDEX] |= 0x80 + + result[VARIANT_BYTE_INDEX] &= 0x3F + result[VARIANT_BYTE_INDEX] |= 0x80 + + return +} + +stamp_v8 :: proc { + stamp_v8_int, + stamp_v8_array, + stamp_v8_slice, +} diff --git a/core/encoding/uuid/writing.odin b/core/encoding/uuid/writing.odin new file mode 100644 index 000000000..499cba72b --- /dev/null +++ b/core/encoding/uuid/writing.odin @@ -0,0 +1,131 @@ +package uuid + +import "base:runtime" +import "core:io" +import "core:strconv" +import "core:strings" + +/* +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 +be faster. + +Inputs: +- w: A writable stream. +- id: The identifier to convert. + +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 { + high_nibble := octet >> 4 + low_nibble := octet & 0xF + + io.write_byte(w, strconv.digits[high_nibble]) or_return + io.write_byte(w, strconv.digits[low_nibble]) or_return + return nil + } + + 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 } + io.write_byte(w, '-') 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 } + io.write_byte(w, '-') or_return + for index in 10 ..< 16 { write_octet(w, id[index]) or_return } + + return nil +} + +/* +Write a UUID in the 8-4-4-4-12 format. + +This procedure performs no error checking on the underlying stream. + +Inputs: +- w: A writable stream. +- 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 { + high_nibble := octet >> 4 + low_nibble := octet & 0xF + + io.write_byte(w, strconv.digits[high_nibble]) + io.write_byte(w, strconv.digits[low_nibble]) + } + + for index in 0 ..< 4 { write_octet(w, id[index]) } + io.write_byte(w, '-') + for index in 4 ..< 6 { write_octet(w, id[index]) } + io.write_byte(w, '-') + for index in 6 ..< 8 { write_octet(w, id[index]) } + io.write_byte(w, '-') + for index in 8 ..< 10 { write_octet(w, id[index]) } + io.write_byte(w, '-') + for index in 10 ..< 16 { write_octet(w, id[index]) } +} + +/* +Convert a UUID to a string in the 8-4-4-4-12 format. + +*Allocates Using Provided Allocator* + +Inputs: +- id: The identifier to convert. +- allocator: (default: context.allocator) +- loc: The caller location for debugging purposes (default: #caller_location) + +Returns: +- str: The allocated and converted string. +- error: An optional allocator error if one occured, `nil` otherwise. +*/ +to_string_allocated :: proc( + id: Identifier, + allocator := context.allocator, + loc := #caller_location, +) -> ( + str: string, + error: runtime.Allocator_Error, +) #optional_allocator_error { + buf := make([]byte, EXPECTED_LENGTH, allocator, loc) or_return + builder := strings.builder_from_bytes(buf[:]) + unsafe_write(strings.to_writer(&builder), id) + return strings.to_string(builder), nil +} + +/* +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. +- loc: The caller location for debugging purposes (default: #caller_location) + +Returns: +- str: The converted string which will be stored in `buffer`. +*/ +to_string_buffer :: proc( + id: Identifier, + buffer: []byte, + loc := #caller_location, +) -> ( + str: string, +) { + assert(len(buffer) >= EXPECTED_LENGTH, "The buffer provided is not at least 32 bytes large.", loc) + builder := strings.builder_from_bytes(buffer) + unsafe_write(strings.to_writer(&builder), id) + return strings.to_string(builder) +} + +to_string :: proc { + to_string_allocated, + to_string_buffer, +} diff --git a/core/encoding/xml/debug_print.odin b/core/encoding/xml/debug_print.odin index be958baaa..acced262a 100644 --- a/core/encoding/xml/debug_print.odin +++ b/core/encoding/xml/debug_print.odin @@ -33,7 +33,7 @@ print :: proc(writer: io.Writer, doc: ^Document) -> (written: int, err: io.Error 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) } } @@ -42,10 +42,10 @@ print :: proc(writer: io.Writer, doc: ^Document) -> (written: int, err: io.Error } if len(doc.elements) > 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/core/encoding/xml/tokenizer.odin b/core/encoding/xml/tokenizer.odin index 0f87c366b..a2bbaf28e 100644 --- a/core/encoding/xml/tokenizer.odin +++ b/core/encoding/xml/tokenizer.odin @@ -126,7 +126,7 @@ error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) { t.error_count += 1 } -@(optimization_mode="speed") +@(optimization_mode="favor_size") advance_rune :: proc(t: ^Tokenizer) { #no_bounds_check { /* @@ -170,7 +170,7 @@ peek_byte :: proc(t: ^Tokenizer, offset := 0) -> byte { return 0 } -@(optimization_mode="speed") +@(optimization_mode="favor_size") skip_whitespace :: proc(t: ^Tokenizer) { for { switch t.ch { @@ -182,7 +182,7 @@ skip_whitespace :: proc(t: ^Tokenizer) { } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") is_letter :: proc(r: rune) -> bool { if r < utf8.RUNE_SELF { switch r { @@ -218,9 +218,7 @@ scan_identifier :: proc(t: ^Tokenizer) -> string { for is_valid_identifier_rune(t.ch) { advance_rune(t) if t.ch == ':' { - /* - A namespaced attr can have at most two parts, `namespace:ident`. - */ + // A namespaced attr can have at most two parts, `namespace:ident`. if namespaced { break } @@ -268,14 +266,10 @@ scan_comment :: proc(t: ^Tokenizer) -> (comment: string, err: Error) { return string(t.src[offset : t.offset - 1]), .None } -/* - Skip CDATA -*/ +// Skip CDATA skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) { if t.read_offset + len(CDATA_START) >= len(t.src) { - /* - Can't be the start of a CDATA tag. - */ + // Can't be the start of a CDATA tag. return .None } @@ -290,9 +284,7 @@ skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) { return .Premature_EOF } - /* - Scan until the end of a CDATA tag. - */ + // Scan until the end of a CDATA tag. if t.read_offset + len(CDATA_END) < len(t.src) { if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END { t.read_offset += len(CDATA_END) @@ -304,7 +296,7 @@ skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) { return } -@(optimization_mode="speed") +@(optimization_mode="favor_size") scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close := false, multiline := true) -> (value: string, err: Error) { err = .None @@ -319,14 +311,10 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close case '<': if peek_byte(t) == '!' { if peek_byte(t, 1) == '[' { - /* - Might be the start of a CDATA tag. - */ + // Might be the start of a CDATA tag. skip_cdata(t) or_return } else if peek_byte(t, 1) == '-' && peek_byte(t, 2) == '-' { - /* - Comment start. Eat comment. - */ + // Comment start. Eat comment. t.read_offset += 3 _ = scan_comment(t) or_return } @@ -342,17 +330,13 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close } if t.ch == close { - /* - If it's not a CDATA or comment, it's the end of this body. - */ + // If it's not a CDATA or comment, it's the end of this body. break loop } advance_rune(t) } - /* - Strip trailing whitespace. - */ + // Strip trailing whitespace. lit := string(t.src[offset : t.offset]) end := len(lit) @@ -369,11 +353,6 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close if consume_close { advance_rune(t) } - - /* - TODO: Handle decoding escape characters and unboxing CDATA. - */ - return lit, err } @@ -384,7 +363,7 @@ peek :: proc(t: ^Tokenizer) -> (token: Token) { return token } -scan :: proc(t: ^Tokenizer) -> Token { +scan :: proc(t: ^Tokenizer, multiline_string := false) -> Token { skip_whitespace(t) offset := t.offset @@ -418,7 +397,7 @@ scan :: proc(t: ^Tokenizer) -> Token { case '"', '\'': kind = .Invalid - lit, err = scan_string(t, t.offset, ch, true, false) + lit, err = scan_string(t, t.offset, ch, true, multiline_string) if err == .None { kind = .String } diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin index 5b4b12948..b9656900f 100644 --- a/core/encoding/xml/xml_reader.odin +++ b/core/encoding/xml/xml_reader.odin @@ -203,9 +203,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha doc.elements = make([dynamic]Element, 1024, 1024, allocator) - // strings.intern_init(&doc.intern, allocator, allocator) - - err = .Unexpected_Token + err = .Unexpected_Token element, parent: Element_ID open: Token @@ -259,8 +257,8 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha case .Slash: // Empty tag. Close it. expect(t, .Gt) or_return - parent = doc.elements[element].parent - element = parent + parent = doc.elements[element].parent + element = parent case: error(t, t.offset, "Expected close tag, got: %#v\n", end_token) @@ -276,8 +274,8 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha error(t, t.offset, "Mismatched Closing Tag. Expected %v, got %v\n", doc.elements[element].ident, ident.text) return doc, .Mismatched_Closing_Tag } - parent = doc.elements[element].parent - element = parent + parent = doc.elements[element].parent + element = parent } else if open.kind == .Exclaim { // (validated: Options, err: Error) { return validated, .None } -expect :: proc(t: ^Tokenizer, kind: Token_Kind) -> (tok: Token, err: Error) { - tok = scan(t) +expect :: proc(t: ^Tokenizer, kind: Token_Kind, multiline_string := false) -> (tok: Token, err: Error) { + tok = scan(t, multiline_string=multiline_string) if tok.kind == kind { return tok, .None } error(t, t.offset, "Expected \"%v\", got \"%v\".", kind, tok.kind) @@ -480,7 +478,13 @@ parse_attribute :: proc(doc: ^Document) -> (attr: Attribute, offset: int, err: E offset = t.offset - len(key.text) _ = expect(t, .Eq) or_return - value := expect(t, .String) or_return + value := expect(t, .String, multiline_string=true) or_return + + normalized, normalize_err := entity.decode_xml(value.text, {.Normalize_Whitespace}, doc.allocator) + if normalize_err == .None { + append(&doc.strings_to_free, normalized) + value.text = normalized + } attr.key = key.text attr.val = value.text diff --git a/core/flags/LICENSE b/core/flags/LICENSE new file mode 100644 index 000000000..e4e21e62d --- /dev/null +++ b/core/flags/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2024, Feoramund + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/core/flags/constants.odin b/core/flags/constants.odin new file mode 100644 index 000000000..ab3dc9a0a --- /dev/null +++ b/core/flags/constants.odin @@ -0,0 +1,38 @@ +package flags + +import "core:time" + +// Set to true to compile with support for core named types disabled, as a +// fallback in the event your platform does not support one of the types, or +// you have no need for them and want a smaller binary. +NO_CORE_NAMED_TYPES :: #config(ODIN_CORE_FLAGS_NO_CORE_NAMED_TYPES, false) + +// Override support for parsing `time` types. +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) + +TAG_ARGS :: "args" +SUBTAG_NAME :: "name" +SUBTAG_POS :: "pos" +SUBTAG_REQUIRED :: "required" +SUBTAG_HIDDEN :: "hidden" +SUBTAG_VARIADIC :: "variadic" +SUBTAG_FILE :: "file" +SUBTAG_PERMS :: "perms" +SUBTAG_INDISTINCT :: "indistinct" + +TAG_USAGE :: "usage" + +UNDOCUMENTED_FLAG :: "" + +INTERNAL_VARIADIC_FLAG :: "varg" + +RESERVED_HELP_FLAG :: "help" +RESERVED_HELP_FLAG_SHORT :: "h" + +// If there are more than this number of flags in total, only the required and +// positional flags will be shown in the one-line usage summary. +ONE_LINE_FLAG_CUTOFF_COUNT :: 16 diff --git a/core/flags/doc.odin b/core/flags/doc.odin new file mode 100644 index 000000000..c3663c419 --- /dev/null +++ b/core/flags/doc.odin @@ -0,0 +1,181 @@ +/* +package flags implements a command-line argument parser. + +It works by using Odin's run-time type information to determine where and how +to store data on a struct provided by the program. Type conversion is handled +automatically and errors are reported with useful messages. + + +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 +``` + + +Struct Tags: + +Users of the `core:encoding/json` package may be familiar with using tags to +annotate struct metadata. The same technique is used here to annotate where +arguments should go and which are required. + +Under the `args` tag, there are the following subtags: + +- `name=S`: set `S` as the flag's name. +- `pos=N`: place positional argument `N` into this flag. +- `hidden`: hide this flag from the usage documentation. +- `required`: cause verification to fail if this argument is not set. +- `variadic`: take all remaining arguments when set, UNIX-style only. +- `file`: for `os.Handle` types, file open mode. +- `perms`: for `os.Handle` types, file open permissions. +- `indistinct`: allow the setting of distinct types by their base type. + +`required` may be given a range specifier in the following formats: +``` +min + ( + error: string, + handled: bool, + alloc_error: runtime.Allocator_Error, +) { + if data_type == Fixed_Point1_1 { + handled = true + ptr := cast(^Fixed_Point1_1)data + + // precision := flags.get_subtag(args_tag, "precision") + + if len(unparsed_value) == 3 { + ptr.integer = unparsed_value[0] - '0' + ptr.fractional = unparsed_value[2] - '0' + } else { + error = "Incorrect format. Must be in the form of `i.f`." + } + + // Perform sanity checking here in the type parsing phase. + // + // The validation phase is flag-specific. + if !(0 <= ptr.integer && ptr.integer < 10) || !(0 <= ptr.fractional && ptr.fractional < 10) { + error = "Incorrect format. Must be between `0.0` and `9.9`." + } + } + + return +} + +my_custom_flag_checker :: proc( + model: rawptr, + name: string, + value: any, + args_tag: string, +) -> (error: string) { + if name == "iterations" { + v := value.(int) + if !(1 <= v && v < 5) { + error = "Iterations only supports 1 ..< 5." + } + } + + return +} + +Distinct_Int :: distinct int + +main :: proc() { + Options :: struct { + + file: os.Handle `args:"pos=0,required,file=r" usage:"Input file."`, + output: os.Handle `args:"pos=1,file=cw" usage:"Output file."`, + + hub: net.Host_Or_Endpoint `usage:"Internet address to contact for updates."`, + schedule: datetime.DateTime `usage:"Launch tasks at this time."`, + + opt: Optimization_Level `usage:"Optimization level."`, + todo: [dynamic]string `usage:"Todo items."`, + + accuracy: Fixed_Point1_1 `args:"required" usage:"Lenience in FLOP calculations."`, + iterations: int `usage:"Run this many times."`, + + // Note how the parser will transform this flag's name into `special-int`. + special_int: Distinct_Int `args:"indistinct" usage:"Able to set distinct types."`, + + quat: quaternion256, + + bits: bit_set[0..<8], + + // Many different requirement styles: + + // gadgets: [dynamic]string `args:"required=1" usage:"gadgets"`, + // widgets: [dynamic]string `args:"required=<3" usage:"widgets"`, + // foos: [dynamic]string `args:"required=2<4"`, + // bars: [dynamic]string `args:"required=3<4"`, + // bots: [dynamic]string `args:"required"`, + + // (Maps) Only available in Odin style: + + // assignments: map[string]u8 `args:"name=assign" usage:"Number of jobs per worker."`, + + // (Variadic) Only available in UNIX style: + + // bots: [dynamic]string `args:"variadic=2,required"`, + + verbose: bool `usage:"Show verbose output."`, + debug: bool `args:"hidden" usage:"print debug info"`, + + varg: [dynamic]string `usage:"Any extra arguments go here."`, + } + + opt: Options + style : flags.Parsing_Style = .Odin + + flags.register_type_setter(my_custom_type_setter) + flags.register_flag_checker(my_custom_flag_checker) + flags.parse_or_exit(&opt, os.args, style) + + fmt.printfln("%#v", opt) + + if opt.output != 0 { + os.write_string(opt.output, "Hellope!\n") + } +} diff --git a/core/flags/internal_assignment.odin b/core/flags/internal_assignment.odin new file mode 100644 index 000000000..bb49977eb --- /dev/null +++ b/core/flags/internal_assignment.odin @@ -0,0 +1,262 @@ +//+private +package flags + +import "base:intrinsics" +@require import "base:runtime" +import "core:container/bit_array" +@require import "core:fmt" +@require import "core:mem" +import "core:reflect" +@require import "core:strconv" +@require import "core:strings" + +// Push a positional argument onto a data struct, checking for specified +// positionals first before adding it to a fallback field. +@(optimization_mode="favor_size") +push_positional :: #force_no_inline proc (model: ^$T, parser: ^Parser, arg: string) -> (error: Error) { + if bit_array.get(&parser.filled_pos, parser.filled_pos.max_index) { + // The max index is set, which means we're out of space. + // Add one free bit by setting the index above to false. + bit_array.set(&parser.filled_pos, 1 + parser.filled_pos.max_index, false) + } + + pos: int = --- + { + iter := bit_array.make_iterator(&parser.filled_pos) + ok: bool + pos, ok = bit_array.iterate_by_unset(&iter) + + // This may be an allocator error. + assert(ok, "Unable to find a free spot in the positional bit_array.") + } + + field, index, has_pos_assigned := get_field_by_pos(model, pos) + + if !has_pos_assigned { + when intrinsics.type_has_field(T, INTERNAL_VARIADIC_FLAG) { + // Add it to the fallback array. + field = reflect.struct_field_by_name(T, INTERNAL_VARIADIC_FLAG) + } else { + return Parse_Error { + .Extra_Positional, + fmt.tprintf("Got extra positional argument `%s` with nowhere to store it.", arg), + } + } + } + + ptr := cast(rawptr)(cast(uintptr)model + field.offset) + args_tag, _ := reflect.struct_tag_lookup(field.tag, TAG_ARGS) + field_name := get_field_name(field) + error = parse_and_set_pointer_by_type(ptr, arg, field.type, args_tag) + #partial switch &specific_error in error { + case Parse_Error: + specific_error.message = fmt.tprintf("Unable to set positional #%i (%s) of type %v to `%s`.%s%s", + pos, + field_name, + field.type, + arg, + " " if len(specific_error.message) > 0 else "", + specific_error.message) + case nil: + bit_array.set(&parser.filled_pos, pos) + bit_array.set(&parser.fields_set, index) + } + + return +} + +register_field :: proc(parser: ^Parser, field: reflect.Struct_Field, index: int) { + if pos, ok := get_field_pos(field); ok { + bit_array.set(&parser.filled_pos, pos) + } + + bit_array.set(&parser.fields_set, index) +} + +// Set a `-flag` argument, Odin-style. +@(optimization_mode="favor_size") +set_odin_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (error: Error) { + // We make a special case for help requests. + switch name { + case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT: + return Help_Request{} + } + + field, index := get_field_by_name(model, name) or_return + + #partial switch specific_type_info in field.type.variant { + case runtime.Type_Info_Boolean: + ptr := cast(^bool)(cast(uintptr)model + field.offset) + ptr^ = true + case: + return Parse_Error { + .Bad_Value, + fmt.tprintf("Unable to set `%s` of type %v to true.", name, field.type), + } + } + + register_field(parser, field, index) + return +} + +// Set a `-flag` argument, UNIX-style. +@(optimization_mode="favor_size") +set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args: int, error: Error) { + // We make a special case for help requests. + switch name { + case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT: + return 0, Help_Request{} + } + + field, index := get_field_by_name(model, name) or_return + + #partial switch specific_type_info in field.type.variant { + case runtime.Type_Info_Boolean: + ptr := cast(^bool)(cast(uintptr)model + field.offset) + ptr^ = true + case runtime.Type_Info_Dynamic_Array: + future_args = 1 + if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok { + if length, is_variadic := get_struct_subtag(tag, SUBTAG_VARIADIC); is_variadic { + // Variadic arrays may specify how many arguments they consume at once. + // Otherwise, they take everything that's left. + if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok { + future_args = cast(int)value + } else { + future_args = max(int) + } + } + } + case: + // `--flag`, waiting on its value. + future_args = 1 + } + + register_field(parser, field, index) + return +} + +// Set a `-flag:option` argument. +@(optimization_mode="favor_size") +set_option :: proc(model: ^$T, parser: ^Parser, name, option: string) -> (error: Error) { + field, index := get_field_by_name(model, name) or_return + + if len(option) == 0 { + return Parse_Error { + .No_Value, + fmt.tprintf("Setting `%s` to an empty value is meaningless.", name), + } + } + + // Guard against incorrect syntax. + #partial switch specific_type_info in field.type.variant { + case runtime.Type_Info_Map: + return Parse_Error { + .No_Value, + fmt.tprintf("Unable to set `%s` of type %v to `%s`. Are you missing an `=`? The correct format is `map:key=value`.", name, field.type, option), + } + } + + ptr := cast(rawptr)(cast(uintptr)model + field.offset) + args_tag := reflect.struct_tag_get(field.tag, TAG_ARGS) + error = parse_and_set_pointer_by_type(ptr, option, field.type, args_tag) + #partial switch &specific_error in error { + case Parse_Error: + specific_error.message = fmt.tprintf("Unable to set `%s` of type %v to `%s`.%s%s", + name, + field.type, + option, + " " if len(specific_error.message) > 0 else "", + specific_error.message) + case nil: + register_field(parser, field, index) + } + + return +} + +// Set a `-map:key=value` argument. +@(optimization_mode="favor_size") +set_key_value :: proc(model: ^$T, parser: ^Parser, name, key, value: string) -> (error: Error) { + field, index := get_field_by_name(model, name) or_return + + #partial switch specific_type_info in field.type.variant { + case runtime.Type_Info_Map: + key := key + key_ptr := cast(rawptr)&key + key_cstr: cstring + if reflect.is_cstring(specific_type_info.key) { + // We clone the key here, because it's liable to be a slice of an + // Odin string, and we need to put a NUL terminator in it. + key_cstr = strings.clone_to_cstring(key) + key_ptr = &key_cstr + } + defer if key_cstr != nil { + delete(key_cstr) + } + + raw_map := (^runtime.Raw_Map)(cast(uintptr)model + field.offset) + + hash := specific_type_info.map_info.key_hasher(key_ptr, runtime.map_seed(raw_map^)) + + backing_alloc := false + elem_backing: []byte + value_ptr: rawptr + + if raw_map.allocator.procedure == nil { + raw_map.allocator = context.allocator + } else { + value_ptr = runtime.__dynamic_map_get(raw_map, + specific_type_info.map_info, + hash, + key_ptr, + ) + } + + if value_ptr == nil { + alloc_error: runtime.Allocator_Error = --- + elem_backing, alloc_error = mem.alloc_bytes(specific_type_info.value.size, specific_type_info.value.align) + if elem_backing == nil { + return Parse_Error { + alloc_error, + "Failed to allocate element backing for map value.", + } + } + + backing_alloc = true + value_ptr = raw_data(elem_backing) + } + + args_tag, _ := reflect.struct_tag_lookup(field.tag, TAG_ARGS) + error = parse_and_set_pointer_by_type(value_ptr, value, specific_type_info.value, args_tag) + #partial switch &specific_error in error { + case Parse_Error: + specific_error.message = fmt.tprintf("Unable to set `%s` of type %v with key=value: `%s`=`%s`.%s%s", + name, + field.type, + key, + value, + " " if len(specific_error.message) > 0 else "", + specific_error.message) + } + + if backing_alloc { + runtime.__dynamic_map_set(raw_map, + specific_type_info.map_info, + hash, + key_ptr, + value_ptr, + ) + + delete(elem_backing) + } + + register_field(parser, field, index) + return + } + + return Parse_Error { + .Bad_Value, + fmt.tprintf("Unable to set `%s` of type %v with key=value: `%s`=`%s`.", name, field.type, key, value), + } +} diff --git a/core/flags/internal_parsing.odin b/core/flags/internal_parsing.odin new file mode 100644 index 000000000..7a769b17c --- /dev/null +++ b/core/flags/internal_parsing.odin @@ -0,0 +1,170 @@ +//+private +package flags + +import "core:container/bit_array" +import "core:strconv" +import "core:strings" + +// Used to group state together. +Parser :: struct { + // `fields_set` tracks which arguments have been set. + // It uses their struct field index. + fields_set: bit_array.Bit_Array, + + // `filled_pos` tracks which arguments have been filled into positional + // spots, much like how `fmt` treats them. + filled_pos: bit_array.Bit_Array, +} + +parse_one_odin_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> (error: Error) { + arg := arg + + if strings.has_prefix(arg, "-") { + arg = arg[1:] + + flag: string + assignment_rune: rune + find_assignment: for r, i in arg { + switch r { + case ':', '=': + assignment_rune = r + flag = arg[:i] + arg = arg[1 + i:] + break find_assignment + case: + continue find_assignment + } + } + + if assignment_rune == 0 { + if len(arg) == 0 { + return Parse_Error { + .No_Flag, + "No flag was given.", + } + } + + // -flag + set_odin_flag(model, parser, arg) or_return + + } else if assignment_rune == ':' { + // -flag:option -map:key=value + error = set_option(model, parser, flag, arg) + + if error != nil { + // -flag:option did not work, so this may be a -map:key=value set. + find_equals: for r, i in arg { + if r == '=' { + key := arg[:i] + arg = arg[1 + i:] + error = set_key_value(model, parser, flag, key, arg) + break find_equals + } + } + } + + } else { + // -flag=option, alternative syntax + set_option(model, parser, flag, arg) or_return + } + + } else { + // positional + error = push_positional(model, parser, arg) + } + + return +} + +parse_one_unix_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> ( + future_args: int, + current_flag: string, + error: Error, +) { + arg := arg + + if strings.has_prefix(arg, "-") { + // -flag + arg = arg[1:] + + if strings.has_prefix(arg, "-") { + // Allow `--` to function as `-`. + arg = arg[1:] + + if len(arg) == 0 { + // `--`, and only `--`. + // Everything from now on will be treated as an argument. + future_args = max(int) + current_flag = INTERNAL_VARIADIC_FLAG + return + } + } + + flag: string + find_assignment: for r, i in arg { + if r == '=' { + // --flag=option + flag = arg[:i] + arg = arg[1 + i:] + error = set_option(model, parser, flag, arg) + return + } + } + + // --flag option, potentially + future_args = set_unix_flag(model, parser, arg) or_return + current_flag = arg + + } else { + // positional + error = push_positional(model, parser, arg) + } + + return +} + +// Parse a number of requirements specifier. +// +// Examples: +// +// `min` +// ` (minimum, maximum: int, ok: bool) { + if len(str) == 0 { + return 1, max(int), true + } + + if less_than := strings.index_byte(str, '<'); less_than != -1 { + if len(str) == 1 { + return 0, 0, false + } + + #no_bounds_check left := str[:less_than] + #no_bounds_check right := str[1 + less_than:] + + if left_value, parse_ok := strconv.parse_u64_of_base(left, 10); parse_ok { + minimum = cast(int)left_value + } else if len(left) > 0 { + return 0, 0, false + } + + if right_value, parse_ok := strconv.parse_u64_of_base(right, 10); parse_ok { + maximum = cast(int)right_value + } else if len(right) > 0 { + return 0, 0, false + } else { + maximum = max(int) + } + } else { + if value, parse_ok := strconv.parse_u64_of_base(str, 10); parse_ok { + minimum = cast(int)value + maximum = max(int) + } else { + return 0, 0, false + } + } + + ok = true + return +} diff --git a/core/flags/internal_rtti.odin b/core/flags/internal_rtti.odin new file mode 100644 index 000000000..ac39eaa8b --- /dev/null +++ b/core/flags/internal_rtti.odin @@ -0,0 +1,529 @@ +//+private +package flags + +import "base:intrinsics" +import "base:runtime" +import "core:fmt" +import "core:mem" +import "core:os" +import "core:reflect" +import "core:strconv" +import "core:strings" +@require import "core:time" +@require import "core:time/datetime" +import "core:unicode/utf8" + +@(optimization_mode="favor_size") +parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info: ^runtime.Type_Info) -> bool { + bounded_int :: proc(value, min, max: i128) -> (result: i128, ok: bool) { + return value, min <= value && value <= max + } + + bounded_uint :: proc(value, max: u128) -> (result: u128, ok: bool) { + return value, value <= max + } + + // NOTE(Feoramund): This procedure has been written with the goal in mind + // of generating the least amount of assembly, given that this library is + // likely to be called once and forgotten. + // + // I've rewritten the switch tables below in 3 different ways, and the + // current one generates the least amount of code for me on Linux AMD64. + // + // The other two ways were: + // + // - the original implementation: use of parametric polymorphism which led + // to dozens of functions generated, one for each type. + // + // - a `value, ok` assignment statement with the `or_return` done at the + // end of the switch, instead of inline. + // + // This seems to be the smallest way for now. + + #partial switch specific_type_info in type_info.variant { + case runtime.Type_Info_Integer: + if specific_type_info.signed { + value := strconv.parse_i128(str) or_return + switch type_info.id { + case i8: (^i8) (ptr)^ = cast(i8) bounded_int(value, cast(i128)min(i8), cast(i128)max(i8) ) or_return + case i16: (^i16) (ptr)^ = cast(i16) bounded_int(value, cast(i128)min(i16), cast(i128)max(i16) ) or_return + case i32: (^i32) (ptr)^ = cast(i32) bounded_int(value, cast(i128)min(i32), cast(i128)max(i32) ) or_return + case i64: (^i64) (ptr)^ = cast(i64) bounded_int(value, cast(i128)min(i64), cast(i128)max(i64) ) or_return + case i128: (^i128) (ptr)^ = value + + case int: (^int) (ptr)^ = cast(int) bounded_int(value, cast(i128)min(int), cast(i128)max(int) ) or_return + + case i16le: (^i16le) (ptr)^ = cast(i16le) bounded_int(value, cast(i128)min(i16le), cast(i128)max(i16le) ) or_return + case i32le: (^i32le) (ptr)^ = cast(i32le) bounded_int(value, cast(i128)min(i32le), cast(i128)max(i32le) ) or_return + case i64le: (^i64le) (ptr)^ = cast(i64le) bounded_int(value, cast(i128)min(i64le), cast(i128)max(i64le) ) or_return + case i128le: (^i128le)(ptr)^ = cast(i128le) bounded_int(value, cast(i128)min(i128le), cast(i128)max(i128le)) or_return + + case i16be: (^i16be) (ptr)^ = cast(i16be) bounded_int(value, cast(i128)min(i16be), cast(i128)max(i16be) ) or_return + case i32be: (^i32be) (ptr)^ = cast(i32be) bounded_int(value, cast(i128)min(i32be), cast(i128)max(i32be) ) or_return + case i64be: (^i64be) (ptr)^ = cast(i64be) bounded_int(value, cast(i128)min(i64be), cast(i128)max(i64be) ) or_return + case i128be: (^i128be)(ptr)^ = cast(i128be) bounded_int(value, cast(i128)min(i128be), cast(i128)max(i128be)) or_return + } + } else { + value := strconv.parse_u128(str) or_return + switch type_info.id { + case u8: (^u8) (ptr)^ = cast(u8) bounded_uint(value, cast(u128)max(u8) ) or_return + case u16: (^u16) (ptr)^ = cast(u16) bounded_uint(value, cast(u128)max(u16) ) or_return + case u32: (^u32) (ptr)^ = cast(u32) bounded_uint(value, cast(u128)max(u32) ) or_return + case u64: (^u64) (ptr)^ = cast(u64) bounded_uint(value, cast(u128)max(u64) ) or_return + case u128: (^u128) (ptr)^ = value + + case uint: (^uint) (ptr)^ = cast(uint) bounded_uint(value, cast(u128)max(uint) ) or_return + case uintptr: (^uintptr)(ptr)^ = cast(uintptr) bounded_uint(value, cast(u128)max(uintptr)) or_return + + case u16le: (^u16le) (ptr)^ = cast(u16le) bounded_uint(value, cast(u128)max(u16le) ) or_return + case u32le: (^u32le) (ptr)^ = cast(u32le) bounded_uint(value, cast(u128)max(u32le) ) or_return + case u64le: (^u64le) (ptr)^ = cast(u64le) bounded_uint(value, cast(u128)max(u64le) ) or_return + case u128le: (^u128le) (ptr)^ = cast(u128le) bounded_uint(value, cast(u128)max(u128le) ) or_return + + case u16be: (^u16be) (ptr)^ = cast(u16be) bounded_uint(value, cast(u128)max(u16be) ) or_return + case u32be: (^u32be) (ptr)^ = cast(u32be) bounded_uint(value, cast(u128)max(u32be) ) or_return + case u64be: (^u64be) (ptr)^ = cast(u64be) bounded_uint(value, cast(u128)max(u64be) ) or_return + case u128be: (^u128be) (ptr)^ = cast(u128be) bounded_uint(value, cast(u128)max(u128be) ) or_return + } + } + + case runtime.Type_Info_Rune: + if utf8.rune_count_in_string(str) != 1 { + return false + } + + (^rune)(ptr)^ = utf8.rune_at_pos(str, 0) + + case runtime.Type_Info_Float: + value := strconv.parse_f64(str) or_return + switch type_info.id { + case f16: (^f16) (ptr)^ = cast(f16) value + case f32: (^f32) (ptr)^ = cast(f32) value + case f64: (^f64) (ptr)^ = value + + case f16le: (^f16le)(ptr)^ = cast(f16le) value + case f32le: (^f32le)(ptr)^ = cast(f32le) value + case f64le: (^f64le)(ptr)^ = cast(f64le) value + + case f16be: (^f16be)(ptr)^ = cast(f16be) value + case f32be: (^f32be)(ptr)^ = cast(f32be) value + case f64be: (^f64be)(ptr)^ = cast(f64be) value + } + + case runtime.Type_Info_Complex: + value := strconv.parse_complex128(str) or_return + switch type_info.id { + case complex32: (^complex32) (ptr)^ = (complex32)(value) + case complex64: (^complex64) (ptr)^ = (complex64)(value) + case complex128: (^complex128)(ptr)^ = value + } + + case runtime.Type_Info_Quaternion: + value := strconv.parse_quaternion256(str) or_return + switch type_info.id { + case quaternion64: (^quaternion64) (ptr)^ = (quaternion64)(value) + case quaternion128: (^quaternion128)(ptr)^ = (quaternion128)(value) + case quaternion256: (^quaternion256)(ptr)^ = value + } + + case runtime.Type_Info_String: + if specific_type_info.is_cstring { + cstr_ptr := (^cstring)(ptr) + if cstr_ptr != nil { + // Prevent memory leaks from us setting this value multiple times. + delete(cstr_ptr^) + } + cstr_ptr^ = strings.clone_to_cstring(str) + } else { + (^string)(ptr)^ = str + } + + case runtime.Type_Info_Boolean: + value := strconv.parse_bool(str) or_return + switch type_info.id { + case bool: (^bool)(ptr)^ = value + case b8: (^b8) (ptr)^ = b8(value) + case b16: (^b16) (ptr)^ = b16(value) + case b32: (^b32) (ptr)^ = b32(value) + case b64: (^b64) (ptr)^ = b64(value) + } + + case runtime.Type_Info_Bit_Set: + // Parse a string of 1's and 0's, from left to right, + // least significant bit to most significant bit. + value: u128 + + // NOTE: `upper` is inclusive, i.e: `0..=31` + max_bit_index := u128(1 + specific_type_info.upper - specific_type_info.lower) + bit_index := u128(0) + #no_bounds_check for string_index in 0.. (error: Error) { + #partial switch specific_type_info in type_info.variant { + case runtime.Type_Info_Named: + if global_custom_type_setter != nil { + // The program gets to go first. + error_message, handled, alloc_error := global_custom_type_setter(ptr, type_info.id, str, arg_tag) + + if alloc_error != nil { + // There was an allocation error. Bail out. + return Parse_Error { + alloc_error, + "Custom type setter encountered allocation error.", + } + } + + if handled { + // The program handled the type. + + if len(error_message) != 0 { + // However, there was an error. Pass it along. + error = Parse_Error { + .Bad_Value, + error_message, + } + } + + return + } + } + + // Might be a named enum. Need to check here first, since we handle all enums. + if enum_type_info, is_enum := specific_type_info.base.variant.(runtime.Type_Info_Enum); is_enum { + if value, ok := reflect.enum_from_name_any(type_info.id, str); ok { + set_unbounded_integer_by_type(ptr, value, enum_type_info.base.id) + } else { + return Parse_Error { + .Bad_Value, + fmt.tprintf("Invalid value name. Valid names are: %s", enum_type_info.names), + } + } + } else { + parse_and_set_pointer_by_named_type(ptr, str, type_info.id, arg_tag, &error) + + if error != nil { + // So far, it's none of the types that we recognize. + // Check to see if we can set it by base type, if allowed. + if _, is_indistinct := get_struct_subtag(arg_tag, SUBTAG_INDISTINCT); is_indistinct { + return parse_and_set_pointer_by_type(ptr, str, specific_type_info.base, arg_tag) + } + } + } + + case runtime.Type_Info_Dynamic_Array: + ptr := cast(^runtime.Raw_Dynamic_Array)ptr + + // Try to convert the value first. + elem_backing, alloc_error := mem.alloc_bytes(specific_type_info.elem.size, specific_type_info.elem.align) + if alloc_error != nil { + return Parse_Error { + alloc_error, + "Failed to allocate element backing for dynamic array.", + } + } + defer delete(elem_backing) + parse_and_set_pointer_by_type(raw_data(elem_backing), str, specific_type_info.elem, arg_tag) or_return + + if !runtime.__dynamic_array_resize(ptr, specific_type_info.elem.size, specific_type_info.elem.align, ptr.len + 1) { + // NOTE: This is purely an assumption that it's OOM. + // Regardless, the resize failed. + return Parse_Error { + runtime.Allocator_Error.Out_Of_Memory, + "Failed to resize dynamic array.", + } + } + + subptr := rawptr( + uintptr(ptr.data) + + uintptr((ptr.len - 1) * specific_type_info.elem.size)) + mem.copy(subptr, raw_data(elem_backing), len(elem_backing)) + + case runtime.Type_Info_Enum: + // This is a nameless enum. + // The code here is virtually the same as above for named enums. + if value, ok := reflect.enum_from_name_any(type_info.id, str); ok { + set_unbounded_integer_by_type(ptr, value, specific_type_info.base.id) + } else { + return Parse_Error { + .Bad_Value, + fmt.tprintf("Invalid value name. Valid names are: %s", specific_type_info.names), + } + } + + case: + if !parse_and_set_pointer_by_base_type(ptr, str, type_info) { + return Parse_Error { + // The caller will add more details. + .Bad_Value, + "", + } + } + } + + return +} + +get_struct_subtag :: get_subtag + +get_field_name :: proc(field: reflect.Struct_Field) -> string { + if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok { + if name_subtag, name_ok := get_struct_subtag(args_tag, SUBTAG_NAME); name_ok { + return name_subtag + } + } + + name, _ := strings.replace_all(field.name, "_", "-", context.temp_allocator) + return name +} + +get_field_pos :: proc(field: reflect.Struct_Field) -> (int, bool) { + if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok { + if pos_subtag, pos_ok := get_struct_subtag(args_tag, SUBTAG_POS); pos_ok { + if value, parse_ok := strconv.parse_u64_of_base(pos_subtag, 10); parse_ok { + return int(value), true + } + } + } + + return 0, false +} + +// Get a struct field by its field name or `name` subtag. +get_field_by_name :: proc(model: ^$T, name: string) -> (result: reflect.Struct_Field, index: int, error: Error) { + for field, i in reflect.struct_fields_zipped(T) { + if get_field_name(field) == name { + return field, i, nil + } + } + + error = Parse_Error { + .Missing_Flag, + fmt.tprintf("Unable to find any flag named `%s`.", name), + } + return +} + +// Get a struct field by its `pos` subtag. +get_field_by_pos :: proc(model: ^$T, pos: int) -> (result: reflect.Struct_Field, index: int, ok: bool) { + for field, i in reflect.struct_fields_zipped(T) { + args_tag := reflect.struct_tag_lookup(field.tag, TAG_ARGS) or_continue + pos_subtag := get_struct_subtag(args_tag, SUBTAG_POS) or_continue + + value, parse_ok := strconv.parse_u64_of_base(pos_subtag, 10) + if parse_ok && cast(int)value == pos { + return field, i, true + } + } + + return +} diff --git a/core/flags/internal_rtti_nonbsd.odin b/core/flags/internal_rtti_nonbsd.odin new file mode 100644 index 000000000..196c27ab8 --- /dev/null +++ b/core/flags/internal_rtti_nonbsd.odin @@ -0,0 +1,31 @@ +//+private +//+build !freebsd !netbsd !openbsd +package flags + +import "core:net" + +// This proc exists purely as a workaround for import restrictions. +// Returns true if caller should return early. +try_net_parse_workaround :: #force_inline proc ( + data_type: typeid, + str: string, + ptr: rawptr, + out_error: ^Error, +) -> bool { + if data_type == net.Host_Or_Endpoint { + addr, net_error := net.parse_hostname_or_endpoint(str) + if net_error != nil { + // We pass along `net.Error` here. + out_error^ = Parse_Error { + net_error, + "Invalid Host/Endpoint.", + } + return true + } + + (cast(^net.Host_Or_Endpoint)ptr)^ = addr + return true + } + + return false +} diff --git a/core/flags/internal_validation.odin b/core/flags/internal_validation.odin new file mode 100644 index 000000000..b71cf9fe7 --- /dev/null +++ b/core/flags/internal_validation.odin @@ -0,0 +1,244 @@ +//+private +package flags + +@require import "base:runtime" +@require import "core:container/bit_array" +@require import "core:fmt" +@require import "core:mem" +@require import "core:os" +@require import "core:reflect" +@require import "core:strconv" +@require import "core:strings" + +// This proc is used to assert that `T` meets the expectations of the library. +@(optimization_mode="favor_size", disabled=ODIN_DISABLE_ASSERT) +validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_location) { + positionals_assigned_so_far: bit_array.Bit_Array + defer bit_array.destroy(&positionals_assigned_so_far) + + check_fields: for field in reflect.struct_fields_zipped(T) { + if style == .Unix { + #partial switch specific_type_info in field.type.variant { + case runtime.Type_Info_Map: + fmt.panicf("%T.%s is a map type, and these are not supported in UNIX-style parsing mode.", + model_type, field.name, loc = loc) + } + } + + name_is_safe := true + defer { + fmt.assertf(name_is_safe, "%T.%s is using a reserved name.", + model_type, field.name, loc = loc) + } + + switch field.name { + case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT: + name_is_safe = false + } + + args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS) + if !ok { + // If it has no args tag, then we've checked all we need to. + // Most of this proc is validating that the subtags are sane. + continue + } + + if name, has_name := get_struct_subtag(args_tag, SUBTAG_NAME); has_name { + fmt.assertf(len(name) > 0, "%T.%s has a zero-length `%s`.", + model_type, field.name, SUBTAG_NAME, loc = loc) + + fmt.assertf(strings.index(name, " ") == -1, "%T.%s has a `%s` with spaces in it.", + model_type, field.name, SUBTAG_NAME, loc = loc) + + switch name { + case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT: + name_is_safe = false + continue check_fields + case: + name_is_safe = true + } + } + + if pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS); has_pos { + #partial switch specific_type_info in field.type.variant { + case runtime.Type_Info_Map: + fmt.panicf("%T.%s has `%s` defined, and this does not make sense on a map type.", + model_type, field.name, SUBTAG_POS, loc = loc) + } + + pos_value, pos_ok := strconv.parse_u64_of_base(pos_str, 10) + fmt.assertf(pos_ok, "%T.%s has `%s` defined as %q but cannot be parsed a base-10 integer >= 0.", + model_type, field.name, SUBTAG_POS, pos_str, loc = loc) + fmt.assertf(!bit_array.get(&positionals_assigned_so_far, pos_value), "%T.%s has `%s` set to #%i, but that position has already been assigned to another flag.", + model_type, field.name, SUBTAG_POS, pos_value, loc = loc) + bit_array.set(&positionals_assigned_so_far, pos_value) + } + + required_min, required_max: int + if requirement, is_required := get_struct_subtag(args_tag, SUBTAG_REQUIRED); is_required { + fmt.assertf(!reflect.is_boolean(field.type), "%T.%s is a required boolean. This is disallowed.", + model_type, field.name, loc = loc) + + fmt.assertf(field.name != INTERNAL_VARIADIC_FLAG, "%T.%s is defined as required. This is disallowed.", + model_type, field.name, loc = loc) + + if len(requirement) > 0 { + if required_min, required_max, ok = parse_requirements(requirement); ok { + #partial switch specific_type_info in field.type.variant { + case runtime.Type_Info_Dynamic_Array: + fmt.assertf(required_min != required_max, "%T.%s has `%s` defined as %q, but the minimum and maximum are the same. Increase the maximum by 1 for an exact number of arguments: (%i<%i)", + model_type, + field.name, + SUBTAG_REQUIRED, + requirement, + required_min, + 1 + required_max, + loc = loc) + + fmt.assertf(required_min < required_max, "%T.%s has `%s` defined as %q, but the minimum and maximum are swapped.", + model_type, field.name, SUBTAG_REQUIRED, requirement, loc = loc) + + case: + fmt.panicf("%T.%s has `%s` defined as %q, but ranges are only supported on dynamic arrays.", + model_type, field.name, SUBTAG_REQUIRED, requirement, loc = loc) + } + } else { + fmt.panicf("%T.%s has `%s` defined as %q, but it cannot be parsed as a valid range.", + model_type, field.name, SUBTAG_REQUIRED, requirement, loc = loc) + } + } + } + + if length, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic { + if value, parse_ok := strconv.parse_u64_of_base(length, 10); parse_ok { + fmt.assertf(value > 0, + "%T.%s has `%s` set to %i. It must be greater than zero.", + model_type, field.name, value, SUBTAG_VARIADIC, loc = loc) + fmt.assertf(value != 1, + "%T.%s has `%s` set to 1. This has no effect.", + model_type, field.name, SUBTAG_VARIADIC, loc = loc) + } + + #partial switch specific_type_info in field.type.variant { + case runtime.Type_Info_Dynamic_Array: + fmt.assertf(style != .Odin, + "%T.%s has `%s` defined, but this only makes sense in UNIX-style parsing mode.", + model_type, field.name, SUBTAG_VARIADIC, loc = loc) + case: + fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.", + model_type, field.name, SUBTAG_VARIADIC, loc = loc) + } + } + + allowed_to_define_file_perms: bool = --- + #partial switch specific_type_info in field.type.variant { + case runtime.Type_Info_Map: + allowed_to_define_file_perms = specific_type_info.value.id == os.Handle + case runtime.Type_Info_Dynamic_Array: + allowed_to_define_file_perms = specific_type_info.elem.id == os.Handle + case: + allowed_to_define_file_perms = field.type.id == os.Handle + } + + if _, has_file := get_struct_subtag(args_tag, SUBTAG_FILE); has_file { + fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `os.Handle` type.", + model_type, field.name, SUBTAG_FILE, loc = loc) + } + + if _, has_perms := get_struct_subtag(args_tag, SUBTAG_PERMS); has_perms { + fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `os.Handle` type.", + model_type, field.name, SUBTAG_PERMS, loc = loc) + } + + #partial switch specific_type_info in field.type.variant { + case runtime.Type_Info_Map: + fmt.assertf(reflect.is_string(specific_type_info.key), "%T.%s is defined as a map[%T]. Only string types are currently supported as map keys.", + model_type, + field.name, + specific_type_info.key) + } + } +} + +// Validate that all the required arguments are set and that the set arguments +// are up to the program's expectations. +@(optimization_mode="favor_size") +validate_arguments :: proc(model: ^$T, parser: ^Parser) -> Error { + check_fields: for field, index in reflect.struct_fields_zipped(T) { + was_set := bit_array.get(&parser.fields_set, index) + + field_name := get_field_name(field) + args_tag := reflect.struct_tag_get(field.tag, TAG_ARGS) + requirement, is_required := get_struct_subtag(args_tag, SUBTAG_REQUIRED) + + required_min, required_max: int + has_requirements: bool + if is_required { + required_min, required_max, has_requirements = parse_requirements(requirement) + } + + if has_requirements && required_min == 0 { + // Allow `0 ptr.len || ptr.len >= required_max { + if required_max == max(int) { + return Validation_Error { + fmt.tprintf("The flag `%s` had %i option%s set, but it requires at least %i.", + field_name, + ptr.len, + "" if ptr.len == 1 else "s", + required_min), + } + } else { + return Validation_Error { + fmt.tprintf("The flag `%s` had %i option%s set, but it requires at least %i and at most %i.", + field_name, + ptr.len, + "" if ptr.len == 1 else "s", + required_min, + required_max - 1), + } + } + } + } else if !was_set { + if is_required { + return Validation_Error { + fmt.tprintf("The required flag `%s` was not set.", field_name), + } + } + + // Not set, not required; moving on. + continue + } + + // All default checks have passed. The program gets a look at it now. + + if global_custom_flag_checker != nil { + ptr := cast(rawptr)(cast(uintptr)model + field.offset) + error := global_custom_flag_checker(model, + field.name, + mem.make_any(ptr, field.type.id), + args_tag) + + if len(error) > 0 { + // The program reported an error message. + return Validation_Error { error } + } + } + } + + return nil +} diff --git a/core/flags/parsing.odin b/core/flags/parsing.odin new file mode 100644 index 000000000..2d8ce34eb --- /dev/null +++ b/core/flags/parsing.odin @@ -0,0 +1,101 @@ +package flags + +@require import "core:container/bit_array" +@require import "core:fmt" + +Parsing_Style :: enum { + // Odin-style: `-flag`, `-flag:option`, `-map:key=value` + Odin, + // UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument repeating-argument` + Unix, +} + +/* +Parse a slice of command-line arguments into an annotated struct. + +*Allocates Using Provided Allocator* + +By default, this proc will only allocate memory outside of its lifetime if it +has to append to a dynamic array, set a map value, or set a cstring. + +The program is expected to free any allocations on `model` as a result of parsing. + +Inputs: +- model: A pointer to an annotated struct with flag definitions. +- args: A slice of strings, usually `os.args[1:]`. +- style: The argument parsing style. +- validate_args: If `true`, will ensure that all required arguments are set if no errors occurred. +- strict: If `true`, will return on first error. Otherwise, parsing continues. +- allocator: (default: context.allocator) +- loc: The caller location for debugging purposes (default: #caller_location) + +Returns: +- error: A union of errors; parsing, file open, a help request, or validation. +*/ +@(optimization_mode="favor_size") +parse :: proc( + model: ^$T, + args: []string, + style: Parsing_Style = .Odin, + validate_args: bool = true, + strict: bool = true, + allocator := context.allocator, + loc := #caller_location, +) -> (error: Error) { + context.allocator = allocator + validate_structure(model^, style, loc) + + parser: Parser + defer { + bit_array.destroy(&parser.filled_pos) + bit_array.destroy(&parser.fields_set) + } + + switch style { + case .Odin: + for arg in args { + error = parse_one_odin_arg(model, &parser, arg) + if strict && error != nil { + return + } + } + + case .Unix: + // Support for `-flag argument (repeating-argument ...)` + future_args: int + current_flag: string + + for i := 0; i < len(args); i += 1 { + #no_bounds_check arg := args[i] + future_args, current_flag, error = parse_one_unix_arg(model, &parser, arg) + if strict && error != nil { + return + } + + for starting_future_args := future_args; future_args > 0; future_args -= 1 { + i += 1 + if i == len(args) { + if future_args == starting_future_args { + return Parse_Error { + .No_Value, + fmt.tprintf("Expected a value for `%s` but none was given.", current_flag), + } + } + break + } + #no_bounds_check arg = args[i] + + error = set_option(model, &parser, current_flag, arg) + if strict && error != nil { + return + } + } + } + } + + if error == nil && validate_args { + return validate_arguments(model, &parser) + } + + return +} diff --git a/core/flags/rtti.odin b/core/flags/rtti.odin new file mode 100644 index 000000000..ce7a23773 --- /dev/null +++ b/core/flags/rtti.odin @@ -0,0 +1,43 @@ +package flags + +import "base:runtime" + +/* +Handle setting custom data types. + +Inputs: +- data: A raw pointer to the field where the data will go. +- data_type: Type information on the underlying field. +- unparsed_value: The unparsed string that the flag is being set to. +- args_tag: The `args` tag from the struct's field. + +Returns: +- error: An error message, or an empty string if no error occurred. +- handled: A boolean indicating if the setter handles this type. +- alloc_error: If an allocation error occurred, return it here. +*/ +Custom_Type_Setter :: #type proc( + data: rawptr, + data_type: typeid, + unparsed_value: string, + args_tag: string, +) -> ( + error: string, + handled: bool, + alloc_error: runtime.Allocator_Error, +) + +@(private) +global_custom_type_setter: Custom_Type_Setter + +/* +Set the global custom type setter. + +Note that only one can be active at a time. + +Inputs: +- setter: The type setter. Pass `nil` to disable any previously set setter. +*/ +register_type_setter :: proc(setter: Custom_Type_Setter) { + global_custom_type_setter = setter +} diff --git a/core/flags/usage.odin b/core/flags/usage.odin new file mode 100644 index 000000000..c42df7576 --- /dev/null +++ b/core/flags/usage.odin @@ -0,0 +1,293 @@ +package flags + +import "base:runtime" +import "core:fmt" +import "core:io" +import "core:reflect" +import "core:slice" +import "core:strconv" +import "core:strings" + +/* +Write out the documentation for the command-line arguments to a stream. + +Inputs: +- out: The stream to write to. +- data_type: The typeid of the data structure to describe. +- program: The name of the program, usually the first argument to `os.args`. +- style: The argument parsing style, required to show flags in the proper style. +*/ +@(optimization_mode="favor_size") +write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", style: Parsing_Style = .Odin) { + // All flags get their tags parsed so they can be reasoned about later. + Flag :: struct { + name: string, + usage: string, + type_description: string, + full_length: int, + pos: int, + required_min, required_max: int, + is_positional: bool, + is_required: bool, + is_boolean: bool, + is_variadic: bool, + variadic_length: int, + } + + // + // POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ... + // + sort_flags :: proc(i, j: Flag) -> slice.Ordering { + // `varg` goes to the end. + if i.name == INTERNAL_VARIADIC_FLAG { + return .Greater + } else if j.name == INTERNAL_VARIADIC_FLAG { + return .Less + } + + // Handle positionals. + if i.is_positional { + if j.is_positional { + return slice.cmp(i.pos, j.pos) + } else { + return .Less + } + } else { + if j.is_positional { + return .Greater + } + } + + // Then required flags. + if i.is_required { + if !j.is_required { + return .Less + } + } else if j.is_required { + return .Greater + } + + // Finally, sort by name. + return slice.cmp(i.name, j.name) + } + + describe_array_requirements :: proc(flag: Flag) -> (spec: string) { + if flag.is_required { + if flag.required_min == flag.required_max - 1 { + spec = fmt.tprintf(", exactly %i", flag.required_min) + } else if flag.required_min > 0 && flag.required_max == max(int) { + spec = fmt.tprintf(", at least %i", flag.required_min) + } else if flag.required_min == 0 && flag.required_max > 1 { + spec = fmt.tprintf(", at most %i", flag.required_max - 1) + } else if flag.required_min > 0 && flag.required_max > 1 { + spec = fmt.tprintf(", between %i and %i", flag.required_min, flag.required_max - 1) + } else { + spec = ", required" + } + } + return + } + + builder := strings.builder_make() + defer strings.builder_destroy(&builder) + + flag_prefix, flag_assignment: string = ---, --- + switch style { + case .Odin: flag_prefix = "-"; flag_assignment = ":" + case .Unix: flag_prefix = "--"; flag_assignment = " " + } + + visible_flags: [dynamic]Flag + defer delete(visible_flags) + + longest_flag_length: int + + for field in reflect.struct_fields_zipped(data_type) { + flag: Flag + + if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok { + if _, is_hidden := get_struct_subtag(args_tag, SUBTAG_HIDDEN); is_hidden { + // Hidden flags stay hidden. + continue + } + if pos_str, is_pos := get_struct_subtag(args_tag, SUBTAG_POS); is_pos { + flag.is_positional = true + if pos, parse_ok := strconv.parse_u64_of_base(pos_str, 10); parse_ok { + flag.pos = cast(int)pos + } + } + if requirement, is_required := get_struct_subtag(args_tag, SUBTAG_REQUIRED); is_required { + flag.is_required = true + flag.required_min, flag.required_max, _ = parse_requirements(requirement) + } + if length_str, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic { + flag.is_variadic = true + if length, parse_ok := strconv.parse_u64_of_base(length_str, 10); parse_ok { + flag.variadic_length = cast(int)length + } + } + } + + flag.name = get_field_name(field) + flag.is_boolean = reflect.is_boolean(field.type) + + if usage, ok := reflect.struct_tag_lookup(field.tag, TAG_USAGE); ok { + flag.usage = usage + } else { + flag.usage = UNDOCUMENTED_FLAG + } + + #partial switch specific_type_info in field.type.variant { + case runtime.Type_Info_Map: + flag.type_description = fmt.tprintf("<%v>=<%v>%s", + specific_type_info.key.id, + specific_type_info.value.id, + ", required" if flag.is_required else "") + + case runtime.Type_Info_Dynamic_Array: + requirement_spec := describe_array_requirements(flag) + + if flag.is_variadic || flag.name == INTERNAL_VARIADIC_FLAG { + if flag.variadic_length == 0 { + flag.type_description = fmt.tprintf("<%v, ...>%s", + specific_type_info.elem.id, + requirement_spec) + } else { + flag.type_description = fmt.tprintf("<%v, %i at once>%s", + specific_type_info.elem.id, + flag.variadic_length, + requirement_spec) + } + } else { + flag.type_description = fmt.tprintf("<%v>%s", specific_type_info.elem.id, + requirement_spec if len(requirement_spec) > 0 else ", multiple") + } + + case: + if flag.is_boolean { + /* + if flag.is_required { + flag.type_description = ", required" + } + */ + } else { + flag.type_description = fmt.tprintf("<%v>%s", + field.type.id, + ", required" if flag.is_required else "") + } + } + + if flag.name == INTERNAL_VARIADIC_FLAG { + flag.full_length = len(flag.type_description) + } else if flag.is_boolean { + flag.full_length = len(flag_prefix) + len(flag.name) + len(flag.type_description) + } else { + flag.full_length = len(flag_prefix) + len(flag.name) + len(flag_assignment) + len(flag.type_description) + } + + longest_flag_length = max(longest_flag_length, flag.full_length) + + append(&visible_flags, flag) + } + + slice.sort_by_cmp(visible_flags[:], sort_flags) + + // All the flags have been figured out now. + + if len(program) > 0 { + keep_it_short := len(visible_flags) >= ONE_LINE_FLAG_CUTOFF_COUNT + + strings.write_string(&builder, "Usage:\n\t") + strings.write_string(&builder, program) + + for flag in visible_flags { + if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_VARIADIC_FLAG) { + continue + } + + strings.write_byte(&builder, ' ') + + if flag.name == INTERNAL_VARIADIC_FLAG { + strings.write_string(&builder, "...") + continue + } + + if !flag.is_required { strings.write_byte(&builder, '[') } + if !flag.is_positional { strings.write_string(&builder, flag_prefix) } + strings.write_string(&builder, flag.name) + if !flag.is_required { strings.write_byte(&builder, ']') } + } + + strings.write_byte(&builder, '\n') + } + + if len(visible_flags) == 0 { + // No visible flags. An unusual situation, but prevent any extra work. + fmt.wprint(out, strings.to_string(builder)) + return + } + + strings.write_string(&builder, "Flags:\n") + + // Divide the positional/required arguments and the non-required arguments. + divider_index := -1 + for flag, i in visible_flags { + if !flag.is_positional && !flag.is_required { + divider_index = i + break + } + } + if divider_index == 0 { + divider_index = -1 + } + + for flag, i in visible_flags { + if i == divider_index { + SPACING :: 2 // Number of spaces before the '|' from below. + strings.write_byte(&builder, '\t') + spacing := strings.repeat(" ", SPACING + longest_flag_length, context.temp_allocator) + strings.write_string(&builder, spacing) + strings.write_string(&builder, "|\n") + } + + strings.write_byte(&builder, '\t') + + if flag.name == INTERNAL_VARIADIC_FLAG { + strings.write_string(&builder, flag.type_description) + } else { + strings.write_string(&builder, flag_prefix) + strings.write_string(&builder, flag.name) + if !flag.is_boolean { + strings.write_string(&builder, flag_assignment) + } + strings.write_string(&builder, flag.type_description) + } + + if strings.contains_rune(flag.usage, '\n') { + // Multi-line usage documentation. Let's make it look nice. + usage_builder := strings.builder_make(context.temp_allocator) + + strings.write_byte(&usage_builder, '\n') + iter := strings.trim_space(flag.usage) + for line in strings.split_lines_iterator(&iter) { + strings.write_string(&usage_builder, "\t\t") + strings.write_string(&usage_builder, strings.trim_left_space(line)) + strings.write_byte(&usage_builder, '\n') + } + + strings.write_string(&builder, strings.to_string(usage_builder)) + } else { + // Single-line usage documentation. + spacing := strings.repeat(" ", + (longest_flag_length) - flag.full_length, + context.temp_allocator) + + strings.write_string(&builder, spacing) + strings.write_string(&builder, " | ") + strings.write_string(&builder, flag.usage) + strings.write_byte(&builder, '\n') + } + } + + fmt.wprint(out, strings.to_string(builder)) +} diff --git a/core/flags/util.odin b/core/flags/util.odin new file mode 100644 index 000000000..f1bd60e75 --- /dev/null +++ b/core/flags/util.odin @@ -0,0 +1,130 @@ +package flags + +import "core:fmt" +@require import "core:os" +@require import "core:path/filepath" +import "core:strings" + +/* +Parse any arguments into an annotated struct or exit if there was an error. + +*Allocates Using Provided Allocator* + +This is a convenience wrapper over `parse` and `print_errors`. + +Inputs: +- model: A pointer to an annotated struct. +- program_args: A slice of strings, usually `os.args`. +- style: The argument parsing style. +- allocator: (default: context.allocator) +- loc: The caller location for debugging purposes (default: #caller_location) +*/ +@(optimization_mode="favor_size") +parse_or_exit :: proc( + model: ^$T, + program_args: []string, + style: Parsing_Style = .Odin, + allocator := context.allocator, + loc := #caller_location, +) { + assert(len(program_args) > 0, "Program arguments slice is empty.", loc) + + program := filepath.base(program_args[0]) + args: []string + + if len(program_args) > 1 { + args = program_args[1:] + } + + error := parse(model, args, style) + if error != nil { + stderr := os.stream_from_handle(os.stderr) + + if len(args) == 0 { + // No arguments entered, and there was an error; show the usage, + // specifically on STDERR. + write_usage(stderr, T, program, style) + fmt.wprintln(stderr) + } + + print_errors(T, error, program, style) + + _, was_help_request := error.(Help_Request) + os.exit(0 if was_help_request else 1) + } +} +/* +Print out any errors that may have resulted from parsing. + +All error messages print to STDERR, while usage goes to STDOUT, if requested. + +Inputs: +- data_type: The typeid of the data structure to describe, if usage is requested. +- error: The error returned from `parse`. +- style: The argument parsing style, required to show flags in the proper style, when usage is shown. +*/ +@(optimization_mode="favor_size") +print_errors :: proc(data_type: typeid, error: Error, program: string, style: Parsing_Style = .Odin) { + stderr := os.stream_from_handle(os.stderr) + stdout := os.stream_from_handle(os.stdout) + + switch specific_error in error { + case Parse_Error: + fmt.wprintfln(stderr, "[%T.%v] %s", specific_error, specific_error.reason, specific_error.message) + case Open_File_Error: + fmt.wprintfln(stderr, "[%T#%i] Unable to open file with perms 0o%o in mode 0x%x: %s", + specific_error, + specific_error.errno, + specific_error.perms, + specific_error.mode, + specific_error.filename) + case Validation_Error: + fmt.wprintfln(stderr, "[%T] %s", specific_error, specific_error.message) + case Help_Request: + write_usage(stdout, data_type, program, style) + } +} +/* +Get the value for a subtag. + +This is useful if you need to parse through the `args` tag for a struct field +on a custom type setter or custom flag checker. + +Example: + + import "core:flags" + import "core:fmt" + + subtag_example :: proc() { + args_tag := "precision=3,signed" + + precision, has_precision := flags.get_subtag(args_tag, "precision") + signed, is_signed := flags.get_subtag(args_tag, "signed") + + fmt.printfln("precision = %q, %t", precision, has_precision) + fmt.printfln("signed = %q, %t", signed, is_signed) + } + +Output: + + precision = "3", true + signed = "", true + +*/ +get_subtag :: proc(tag, id: string) -> (value: string, ok: bool) { + // This proc was initially private in `internal_rtti.odin`, but given how + // useful it would be to custom type setters and flag checkers, it lives + // here now. + + tag := tag + + for subtag in strings.split_iterator(&tag, ",") { + if equals := strings.index_byte(subtag, '='); equals != -1 && id == subtag[:equals] { + return subtag[1 + equals:], true + } else if id == subtag { + return "", true + } + } + + return +} diff --git a/core/flags/validation.odin b/core/flags/validation.odin new file mode 100644 index 000000000..e370cff48 --- /dev/null +++ b/core/flags/validation.odin @@ -0,0 +1,37 @@ +package flags + +/* +Check a flag after parsing, during the validation stage. + +Inputs: +- model: A raw pointer to the data structure provided to `parse`. +- name: The name of the flag being checked. +- value: An `any` type that contains the value to be checked. +- args_tag: The `args` tag from within the struct. + +Returns: +- error: An error message, or an empty string if no error occurred. +*/ +Custom_Flag_Checker :: #type proc( + model: rawptr, + name: string, + value: any, + args_tag: string, +) -> ( + error: string, +) + +@(private) +global_custom_flag_checker: Custom_Flag_Checker + +/* +Set the global custom flag checker. + +Note that only one can be active at a time. + +Inputs: +- checker: The flag checker. Pass `nil` to disable any previously set checker. +*/ +register_flag_checker :: proc(checker: Custom_Flag_Checker) { + global_custom_flag_checker = checker +} diff --git a/core/fmt/doc.odin b/core/fmt/doc.odin index 597342e76..d45e6c796 100644 --- a/core/fmt/doc.odin +++ b/core/fmt/doc.odin @@ -1,5 +1,5 @@ /* -package fmt implemented formatted I/O with procedures similar to C's printf and Python's format. +package fmt implements formatted I/O with procedures similar to C's printf and Python's format. The format 'verbs' are derived from C's but simpler. Printing @@ -9,6 +9,7 @@ The verbs: General: %v the value in a default format %#v an expanded format of %v with newlines and indentation + %w an Odin-syntax representation of the value %T an Odin-syntax representation of the type of the value %% a literal percent sign; consumes no value {{ a literal open brace; consumes no value @@ -33,6 +34,8 @@ Floating-point, complex numbers, and quaternions: %E scientific notation, e.g. -1.23456E+78 %f decimal point but no exponent, e.g. 123.456 %F synonym for %f + %g synonym for %f with default maximum precision + %G synonym for %g %h hexadecimal (lower-case) representation with 0h prefix (0h01234abcd) %H hexadecimal (upper-case) representation with 0H prefix (0h01234ABCD) %m number of bytes in the best unit of measurement, e.g. 123.45mib @@ -61,9 +64,9 @@ For compound values, the elements are printed using these rules recursively; lai bit sets {key0 = elem0, key1 = elem1, ...} pointer to above: &{}, &[], &map[] -Width is specified by an optional decimal number immediately preceding the verb. +Width is specified by an optional decimal number immediately after the '%'. If not present, the width is whatever is necessary to represent the value. -Precision is specified after the (optional) width followed by a period followed by a decimal number. +Precision is specified after the (optional) width by a period followed by a decimal number. If no period is present, a default precision is used. A period with no following number specifies a precision of 0. @@ -75,7 +78,7 @@ Examples: %8.f width 8, precision 0 Width and precision are measured in units of Unicode code points (runes). -n.b. C's printf uses units of bytes +n.b. C's printf uses units of bytes. Other flags: @@ -92,7 +95,7 @@ Other flags: 0 pad with leading zeros rather than spaces -Flags are ignored by verbs that don't expect them +Flags are ignored by verbs that don't expect them. For each printf-like procedure, there is a print function that takes no @@ -105,19 +108,20 @@ Explicit argument indices: In printf-like procedures, the default behaviour is for each formatting verb to format successive arguments passed in the call. However, the notation [n] immediately before the verb indicates that the nth zero-index argument is to be formatted instead. -The same notation before an '*' for a width or precision selecting the argument index holding the value. -Python-like syntax with argument indices differs for the selecting the argument index: {N:v} +The same notation before an '*' for a width or precision specifier selects the argument index +holding the value. +Python-like syntax with argument indices differs for selecting the argument index: {n:v} Examples: - fmt.printf("%[1]d %[0]d\n", 13, 37); // C-like syntax - fmt.printf("{1:d} {0:d}\n", 13, 37); // Python-like syntax + fmt.printfln("%[1]d %[0]d", 13, 37) // C-like syntax + fmt.printfln("{1:d} {0:d}", 13, 37) // Python-like syntax prints "37 13", whilst: - fmt.printf("%[2]*.[1]*[0]f\n", 17.0, 2, 6); // C-like syntax - fmt.printf("%{0:[2]*.[1]*f}\n", 17.0, 2, 6); // Python-like syntax -equivalent to: - fmt.printf("%6.2f\n", 17.0, 2, 6); // C-like syntax - fmt.printf("{:6.2f}\n", 17.0, 2, 6); // Python-like syntax -prints "17.00" + fmt.printfln("%*[2].*[1][0]f", 17.0, 2, 6) // C-like syntax + fmt.printfln("{0:*[2].*[1]f}", 17.0, 2, 6) // Python-like syntax +is equivalent to: + fmt.printfln("%6.2f", 17.0) // C-like syntax + fmt.printfln("{:6.2f}", 17.0) // Python-like syntax +and prints "17.00". Format errors: diff --git a/core/fmt/example.odin b/core/fmt/example.odin new file mode 100644 index 000000000..503e64f2b --- /dev/null +++ b/core/fmt/example.odin @@ -0,0 +1,57 @@ +//+build ignore +package custom_formatter_example +import "core:fmt" +import "core:io" + +SomeType :: struct { + value: int, +} + +My_Custom_Base_Type :: distinct u32 + +main :: proc() { + // Ensure the fmt._user_formatters map is initialized + fmt.set_user_formatters(new(map[typeid]fmt.User_Formatter)) + + // Register custom formatters for my favorite types + err := fmt.register_user_formatter(type_info_of(SomeType).id, SomeType_Formatter) + assert(err == .None) + err = fmt.register_user_formatter(type_info_of(My_Custom_Base_Type).id, My_Custom_Base_Formatter) + assert(err == .None) + + // Use the custom formatters. + fmt.printfln("SomeType{{42}}: '%v'", SomeType{42}) + fmt.printfln("My_Custom_Base_Type(0xdeadbeef): '%v'", My_Custom_Base_Type(0xdeadbeef)) +} + +SomeType_Formatter :: proc(fi: ^fmt.Info, arg: any, verb: rune) -> bool { + m := cast(^SomeType)arg.data + switch verb { + case 'v', 'd': // We handle `%v` and `%d` + fmt.fmt_int(fi, u64(m.value), true, 8 * size_of(SomeType), verb) + case: + return false + } + return true +} + +My_Custom_Base_Formatter :: proc(fi: ^fmt.Info, arg: any, verb: rune) -> bool { + m := cast(^My_Custom_Base_Type)arg.data + switch verb { + case 'v', 'b': + value := u64(m^) + for value > 0 { + if value & 1 == 1 { + io.write_string(fi.writer, "Hellope!", &fi.n) + } else { + io.write_string(fi.writer, "Hellope?", &fi.n) + } + value >>= 1 + } + + case: + return false + } + return true +} + diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index ba749d102..9aa9c99dc 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -2,6 +2,7 @@ package fmt import "base:intrinsics" import "base:runtime" +import "core:math" import "core:math/bits" import "core:mem" import "core:io" @@ -13,22 +14,7 @@ import "core:unicode/utf8" // Internal data structure that stores the required information for formatted printing Info :: struct { - minus: bool, - plus: bool, - space: bool, - zero: bool, - hash: bool, - width_set: bool, - prec_set: bool, - - width: int, - prec: int, - indent: int, - - reordered: bool, - good_arg_index: bool, - ignore_user_formatters: bool, - in_bad: bool, + using state: Info_State, writer: io.Writer, arg: any, // Temporary @@ -41,6 +27,24 @@ Info :: struct { n: int, // bytes written } +Info_State :: struct { + minus: bool, + plus: bool, + space: bool, + zero: bool, + hash: bool, + width_set: bool, + prec_set: bool, + + ignore_user_formatters: bool, + in_bad: bool, + + width: int, + prec: int, + indent: int, +} + + // Custom formatter signature. It returns true if the formatting was successful and false when it could not be done User_Formatter :: #type proc(fi: ^Info, arg: any, verb: rune) -> bool @@ -88,7 +92,7 @@ _user_formatters: ^map[typeid]User_Formatter // set_user_formatters :: proc(m: ^map[typeid]User_Formatter) { assert(_user_formatters == nil, "set_user_formatters must not be called more than once.") - _user_formatters = m + _user_formatters = m } // Registers a user-defined formatter for a specific typeid // @@ -120,11 +124,11 @@ register_user_formatter :: proc(id: typeid, formatter: User_Formatter) -> Regist // // Returns: A formatted string. // +@(require_results) aprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> string { str: strings.Builder strings.builder_init(&str, allocator) - sbprint(&str, ..args, sep=sep) - return strings.to_string(str) + return sbprint(&str, ..args, sep=sep) } // Creates a formatted string with a newline character at the end // @@ -136,11 +140,11 @@ aprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> strin // // Returns: A formatted string with a newline character at the end. // +@(require_results) aprintln :: proc(args: ..any, sep := " ", allocator := context.allocator) -> string { str: strings.Builder strings.builder_init(&str, allocator) - sbprintln(&str, ..args, sep=sep) - return strings.to_string(str) + return sbprintln(&str, ..args, sep=sep) } // Creates a formatted string using a format string and arguments // @@ -153,11 +157,11 @@ aprintln :: proc(args: ..any, sep := " ", allocator := context.allocator) -> str // // Returns: A formatted string. The returned string must be freed accordingly. // +@(require_results) aprintf :: proc(fmt: string, args: ..any, allocator := context.allocator, newline := false) -> string { str: strings.Builder strings.builder_init(&str, allocator) - sbprintf(&str, fmt, ..args, newline=newline) - return strings.to_string(str) + return sbprintf(&str, fmt, ..args, newline=newline) } // Creates a formatted string using a format string and arguments, followed by a newline. // @@ -169,6 +173,7 @@ aprintf :: proc(fmt: string, args: ..any, allocator := context.allocator, newlin // // Returns: A formatted string. The returned string must be freed accordingly. // +@(require_results) aprintfln :: proc(fmt: string, args: ..any, allocator := context.allocator) -> string { return aprintf(fmt, ..args, allocator=allocator, newline=true) } @@ -182,11 +187,11 @@ aprintfln :: proc(fmt: string, args: ..any, allocator := context.allocator) -> s // // Returns: A formatted string. // +@(require_results) tprint :: proc(args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) - sbprint(&str, ..args, sep=sep) - return strings.to_string(str) + return sbprint(&str, ..args, sep=sep) } // Creates a formatted string with a newline character at the end // @@ -198,11 +203,11 @@ tprint :: proc(args: ..any, sep := " ") -> string { // // Returns: A formatted string with a newline character at the end. // +@(require_results) tprintln :: proc(args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) - sbprintln(&str, ..args, sep=sep) - return strings.to_string(str) + return sbprintln(&str, ..args, sep=sep) } // Creates a formatted string using a format string and arguments // @@ -215,11 +220,11 @@ tprintln :: proc(args: ..any, sep := " ") -> string { // // Returns: A formatted string. // +@(require_results) tprintf :: proc(fmt: string, args: ..any, newline := false) -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) - sbprintf(&str, fmt, ..args, newline=newline) - return strings.to_string(str) + return sbprintf(&str, fmt, ..args, newline=newline) } // Creates a formatted string using a format string and arguments, followed by a newline. // @@ -231,6 +236,7 @@ tprintf :: proc(fmt: string, args: ..any, newline := false) -> string { // // Returns: A formatted string. // +@(require_results) tprintfln :: proc(fmt: string, args: ..any) -> string { return tprintf(fmt, ..args, newline=true) } @@ -339,6 +345,7 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { // // Returns: A formatted C string // +@(require_results) caprintf :: proc(format: string, args: ..any, newline := false) -> cstring { str: strings.Builder strings.builder_init(&str) @@ -357,9 +364,29 @@ caprintf :: proc(format: string, args: ..any, newline := false) -> cstring { // // Returns: A formatted C string // +@(require_results) caprintfln :: proc(format: string, args: ..any) -> cstring { return caprintf(format, ..args, newline=true) } +// Creates a formatted C string +// +// *Allocates Using Context's Temporary Allocator* +// +// Inputs: +// - args: A variadic list of arguments to be formatted. +// - sep: An optional separator string (default is a single space). +// +// Returns: A formatted C string. +// +@(require_results) +ctprint :: proc(args: ..any, sep := " ") -> cstring { + str: strings.Builder + strings.builder_init(&str, context.temp_allocator) + sbprint(&str, ..args, sep=sep) + strings.write_byte(&str, 0) + s := strings.to_string(str) + return cstring(raw_data(s)) +} // Creates a formatted C string // // *Allocates Using Context's Temporary Allocator* @@ -371,6 +398,7 @@ caprintfln :: proc(format: string, args: ..any) -> cstring { // // Returns: A formatted C string // +@(require_results) ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { str: strings.Builder strings.builder_init(&str, context.temp_allocator) @@ -389,6 +417,7 @@ ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { // // Returns: A formatted C string // +@(require_results) ctprintfln :: proc(format: string, args: ..any) -> cstring { return ctprintf(format, ..args, newline=true) } @@ -521,13 +550,107 @@ wprintln :: proc(w: io.Writer, args: ..any, sep := " ", flush := true) -> int { // Returns: The number of bytes written // wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true, newline := false) -> int { + MAX_CHECKED_ARGS :: 64 + assert(len(args) <= MAX_CHECKED_ARGS, "number of args > 64 is unsupported") + + parse_options :: proc(fi: ^Info, fmt: string, index, end: int, unused_args: ^bit_set[0 ..< MAX_CHECKED_ARGS], args: ..any) -> int { + i := index + + // Prefix + prefix_loop: for ; i < end; i += 1 { + switch fmt[i] { + case '+': + fi.plus = true + case '-': + fi.minus = true + fi.zero = false + case ' ': + fi.space = true + case '#': + fi.hash = true + case '0': + fi.zero = !fi.minus + case: + break prefix_loop + } + } + + // Width + if i < end && fmt[i] == '*' { + i += 1 + width_index, _, index_ok := _arg_number(fmt, &i, len(args)) + + if index_ok { + unused_args^ -= {width_index} + + fi.width, _, fi.width_set = int_from_arg(args, width_index) + if !fi.width_set { + io.write_string(fi.writer, "%!(BAD WIDTH)", &fi.n) + } + + if fi.width < 0 { + fi.width = -fi.width + fi.minus = true + fi.zero = false + } + } + } else { + fi.width, i, fi.width_set = _parse_int(fmt, i) + } + + // Precision + if i < end && fmt[i] == '.' { + i += 1 + if i < end && fmt[i] == '*' { + i += 1 + precision_index, _, index_ok := _arg_number(fmt, &i, len(args)) + + if index_ok { + unused_args^ -= {precision_index} + fi.prec, _, fi.prec_set = int_from_arg(args, precision_index) + if fi.prec < 0 { + fi.prec = 0 + fi.prec_set = false + } + if !fi.prec_set { + io.write_string(fi.writer, "%!(BAD PRECISION)", &fi.n) + } + } + } else { + prev_i := i + fi.prec, i, fi.prec_set = _parse_int(fmt, i) + if i == prev_i { + fi.prec = 0 + fi.prec_set = true + } + } + } + + return i + } + + error_check_arg :: proc(fi: ^Info, arg_parsed: bool, unused_args: bit_set[0 ..< MAX_CHECKED_ARGS]) -> (int, bool) { + if !arg_parsed { + for index in unused_args { + return index, true + } + io.write_string(fi.writer, "%!(MISSING ARGUMENT)", &fi.n) + } else { + io.write_string(fi.writer, "%!(BAD ARGUMENT NUMBER)", &fi.n) + } + + return 0, false + } + fi: Info - arg_index: int = 0 end := len(fmt) - was_prev_index := false + unused_args: bit_set[0 ..< MAX_CHECKED_ARGS] + for i in 0 ..< len(args) { + unused_args += {i} + } loop: for i := 0; i < end; /**/ { - fi = Info{writer = w, good_arg_index = true, reordered = fi.reordered, n = fi.n} + fi = Info{writer = w, n = fi.n} prev_i := i for i < end && !(fmt[i] == '%' || fmt[i] == '{' || fmt[i] == '}') { @@ -561,191 +684,65 @@ wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true, newline : } if char == '%' { - prefix_loop: for ; i < end; i += 1 { - switch fmt[i] { - case '+': - fi.plus = true - case '-': - fi.minus = true - fi.zero = false - case ' ': - fi.space = true - case '#': - fi.hash = true - case '0': - fi.zero = !fi.minus - case: - break prefix_loop - } - } - - arg_index, i, was_prev_index = _arg_number(&fi, arg_index, fmt, i, len(args)) - - // Width - if i < end && fmt[i] == '*' { + if i < end && fmt[i] == '%' { + io.write_byte(fi.writer, '%', &fi.n) i += 1 - fi.width, arg_index, fi.width_set = int_from_arg(args, arg_index) - if !fi.width_set { - io.write_string(w, "%!(BAD WIDTH)", &fi.n) - } - - if fi.width < 0 { - fi.width = -fi.width - fi.minus = true - fi.zero = false - } - was_prev_index = false - } else { - fi.width, i, fi.width_set = _parse_int(fmt, i) - if was_prev_index && fi.width_set { // %[6]2d - fi.good_arg_index = false - } + continue loop } - // Precision - if i < end && fmt[i] == '.' { - i += 1 - if was_prev_index { // %[6].2d - fi.good_arg_index = false - } - if i < end && fmt[i] == '*' { - arg_index, i, was_prev_index = _arg_number(&fi, arg_index, fmt, i, len(args)) - i += 1 - fi.prec, arg_index, fi.prec_set = int_from_arg(args, arg_index) - if fi.prec < 0 { - fi.prec = 0 - fi.prec_set = false - } - if !fi.prec_set { - io.write_string(fi.writer, "%!(BAD PRECISION)", &fi.n) - } - was_prev_index = false - } else { - fi.prec, i, fi.prec_set = _parse_int(fmt, i) - } - } + i = parse_options(&fi, fmt, i, end, &unused_args, ..args) - if !was_prev_index { - arg_index, i, was_prev_index = _arg_number(&fi, arg_index, fmt, i, len(args)) + arg_index, arg_parsed, index_ok := _arg_number(fmt, &i, len(args)) + + if !index_ok { + arg_index, index_ok = error_check_arg(&fi, arg_parsed, unused_args) } if i >= end { io.write_string(fi.writer, "%!(NO VERB)", &fi.n) break loop + } else if fmt[i] == ' ' { + io.write_string(fi.writer, "%!(NO VERB)", &fi.n) + continue loop } verb, w := utf8.decode_rune_in_string(fmt[i:]) i += w - switch { - case verb == '%': - io.write_byte(fi.writer, '%', &fi.n) - case !fi.good_arg_index: - io.write_string(fi.writer, "%!(BAD ARGUMENT NUMBER)", &fi.n) - case arg_index >= len(args): - io.write_string(fi.writer, "%!(MISSING ARGUMENT)", &fi.n) - case: + if index_ok { + unused_args -= {arg_index} fmt_arg(&fi, args[arg_index], verb) - arg_index += 1 } } else if char == '{' { + arg_index: int + arg_parsed, index_ok: bool + if i < end && fmt[i] != '}' && fmt[i] != ':' { - new_arg_index, new_i, ok := _parse_int(fmt, i) - if ok { - fi.reordered = true - was_prev_index = true - arg_index = new_arg_index - i = new_i - } else { - io.write_string(fi.writer, "%!(BAD ARGUMENT NUMBER ", &fi.n) - // Skip over the bad argument - start_index := i - for i < end && fmt[i] != '}' && fmt[i] != ':' { - i += 1 - } - fmt_arg(&fi, fmt[start_index:i], 'v') - io.write_string(fi.writer, ")", &fi.n) + arg_index, i, arg_parsed = _parse_int(fmt, i) + if arg_parsed { + index_ok = 0 <= arg_index && arg_index < len(args) } } + if !index_ok { + arg_index, index_ok = error_check_arg(&fi, arg_parsed, unused_args) + } + verb: rune = 'v' if i < end && fmt[i] == ':' { i += 1 - prefix_loop_percent: for ; i < end; i += 1 { - switch fmt[i] { - case '+': - fi.plus = true - case '-': - fi.minus = true - fi.zero = false - case ' ': - fi.space = true - case '#': - fi.hash = true - case '0': - fi.zero = !fi.minus - case: - break prefix_loop_percent - } - } - - arg_index, i, was_prev_index = _arg_number(&fi, arg_index, fmt, i, len(args)) - - // Width - if i < end && fmt[i] == '*' { - i += 1 - fi.width, arg_index, fi.width_set = int_from_arg(args, arg_index) - if !fi.width_set { - io.write_string(fi.writer, "%!(BAD WIDTH)", &fi.n) - } - - if fi.width < 0 { - fi.width = -fi.width - fi.minus = true - fi.zero = false - } - was_prev_index = false - } else { - fi.width, i, fi.width_set = _parse_int(fmt, i) - if was_prev_index && fi.width_set { // %[6]2d - fi.good_arg_index = false - } - } - - // Precision - if i < end && fmt[i] == '.' { - i += 1 - if was_prev_index { // %[6].2d - fi.good_arg_index = false - } - if i < end && fmt[i] == '*' { - arg_index, i, was_prev_index = _arg_number(&fi, arg_index, fmt, i, len(args)) - i += 1 - fi.prec, arg_index, fi.prec_set = int_from_arg(args, arg_index) - if fi.prec < 0 { - fi.prec = 0 - fi.prec_set = false - } - if !fi.prec_set { - io.write_string(fi.writer, "%!(BAD PRECISION)", &fi.n) - } - was_prev_index = false - } else { - fi.prec, i, fi.prec_set = _parse_int(fmt, i) - } - } - - if !was_prev_index { - arg_index, i, was_prev_index = _arg_number(&fi, arg_index, fmt, i, len(args)) - } - + i = parse_options(&fi, fmt, i, end, &unused_args, ..args) if i >= end { io.write_string(fi.writer, "%!(NO VERB)", &fi.n) break loop + } else if fmt[i] == '}' { + i += 1 + io.write_string(fi.writer, "%!(NO VERB)", &fi.n) + continue } w: int = 1 @@ -764,31 +761,35 @@ wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true, newline : switch { case brace != '}': io.write_string(fi.writer, "%!(MISSING CLOSE BRACE)", &fi.n) - case !fi.good_arg_index: - io.write_string(fi.writer, "%!(BAD ARGUMENT NUMBER)", &fi.n) - case arg_index >= len(args): - io.write_string(fi.writer, "%!(MISSING ARGUMENT)", &fi.n) - case: + case index_ok: fmt_arg(&fi, args[arg_index], verb) - arg_index += 1 + unused_args -= {arg_index} } } } - if !fi.reordered && arg_index < len(args) { - io.write_string(fi.writer, "%!(EXTRA ", &fi.n) - for arg, index in args[arg_index:] { - if index > 0 { - io.write_string(fi.writer, ", ", &fi.n) + if unused_args != {} { + // Use default options when formatting extra arguments. + extra_fi := Info { writer = fi.writer, n = fi.n } + + io.write_string(extra_fi.writer, "%!(EXTRA ", &extra_fi.n) + first_printed := false + for index in unused_args { + if first_printed { + io.write_string(extra_fi.writer, ", ", &extra_fi.n) } + arg := args[index] if arg == nil { - io.write_string(fi.writer, "", &fi.n) + io.write_string(extra_fi.writer, "", &extra_fi.n) } else { - fmt_arg(&fi, args[index], 'v') + fmt_arg(&extra_fi, arg, 'v') } + first_printed = true } - io.write_string(fi.writer, ")", &fi.n) + io.write_byte(extra_fi.writer, ')', &extra_fi.n) + + fi.n = extra_fi.n } if newline { @@ -871,18 +872,16 @@ _parse_int :: proc(s: string, offset: int) -> (result: int, new_offset: int, ok: // Parses an argument number from a format string and determines if it's valid // // Inputs: -// - fi: A pointer to an Info structure -// - arg_index: The current argument index // - format: The format string to parse -// - offset: The current position in the format string +// - offset: A pointer to the current position in the format string // - arg_count: The total number of arguments // // Returns: // - index: The parsed argument index -// - new_offset: The new position in the format string -// - ok: A boolean indicating if the parsed argument number is valid +// - parsed: A boolean indicating if an argument number was parsed +// - ok: A boolean indicating if the parsed argument number is within arg_count // -_arg_number :: proc(fi: ^Info, arg_index: int, format: string, offset, arg_count: int) -> (index, new_offset: int, ok: bool) { +_arg_number :: proc(format: string, offset: ^int, arg_count: int) -> (index: int, parsed, ok: bool) { parse_arg_number :: proc(format: string) -> (int, int, bool) { if len(format) < 3 { return 0, 1, false @@ -890,30 +889,28 @@ _arg_number :: proc(fi: ^Info, arg_index: int, format: string, offset, arg_count for i in 1..", &fi.n) } @@ -1022,6 +1019,33 @@ _fmt_int :: proc(fi: ^Info, u: u64, base: int, is_signed: bool, bit_size: int, d } } + buf: [256]byte + start := 0 + + if fi.hash && !is_signed { + switch base { + case 2: + io.write_byte(fi.writer, '0', &fi.n) + io.write_byte(fi.writer, 'b', &fi.n) + start = 2 + + case 8: + io.write_byte(fi.writer, '0', &fi.n) + io.write_byte(fi.writer, 'o', &fi.n) + start = 2 + + case 12: + io.write_byte(fi.writer, '0', &fi.n) + io.write_byte(fi.writer, 'o', &fi.n) + start = 2 + + case 16: + io.write_byte(fi.writer, '0', &fi.n) + io.write_byte(fi.writer, 'x', &fi.n) + start = 2 + } + } + prec := 0 if fi.prec_set { prec = fi.prec @@ -1047,14 +1071,10 @@ _fmt_int :: proc(fi: ^Info, u: u64, base: int, is_signed: bool, bit_size: int, d panic("_fmt_int: unknown base, whoops") } - buf: [256]byte - start := 0 - flags: strconv.Int_Flags - if fi.hash { flags |= {.Prefix} } - if fi.plus { flags |= {.Plus} } + if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} } + if fi.plus { flags += {.Plus} } s := strconv.append_bits(buf[start:], u, base, is_signed, bit_size, digits, flags) - prev_zero := fi.zero defer fi.zero = prev_zero fi.zero = false @@ -1084,6 +1104,33 @@ _fmt_int_128 :: proc(fi: ^Info, u: u128, base: int, is_signed: bool, bit_size: i } } + buf: [256]byte + start := 0 + + if fi.hash && !is_signed { + switch base { + case 2: + io.write_byte(fi.writer, '0', &fi.n) + io.write_byte(fi.writer, 'b', &fi.n) + start = 2 + + case 8: + io.write_byte(fi.writer, '0', &fi.n) + io.write_byte(fi.writer, 'o', &fi.n) + start = 2 + + case 12: + io.write_byte(fi.writer, '0', &fi.n) + io.write_byte(fi.writer, 'o', &fi.n) + start = 2 + + case 16: + io.write_byte(fi.writer, '0', &fi.n) + io.write_byte(fi.writer, 'x', &fi.n) + start = 2 + } + } + prec := 0 if fi.prec_set { prec = fi.prec @@ -1109,12 +1156,9 @@ _fmt_int_128 :: proc(fi: ^Info, u: u128, base: int, is_signed: bool, bit_size: i panic("_fmt_int: unknown base, whoops") } - buf: [256]byte - start := 0 - flags: strconv.Int_Flags - if fi.hash && !fi.zero { flags |= {.Prefix} } - if fi.plus { flags |= {.Plus} } + if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} } + if fi.plus { flags += {.Plus} } s := strconv.append_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags) if fi.hash && fi.zero && fi.indent == 0 { @@ -1185,10 +1229,10 @@ _fmt_memory :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, units: st // Add the unit at the end. copy(buf[len(str):], units[off:off+unit_len]) str = string(buf[:len(str)+unit_len]) - - if !fi.plus { - // Strip sign from "+" but not "+Inf". - if str[0] == '+' && str[1] != 'I' { + + if !fi.plus { + // Strip sign from "+" but not "+Inf". + if str[0] == '+' && str[1] != 'I' { str = str[1:] } } @@ -1416,13 +1460,10 @@ fmt_string :: proc(fi: ^Info, s: string, verb: rune) { if !fi.minus { io.write_string(fi.writer, s, &fi.n) } - } - else { + } else { io.write_string(fi.writer, s, &fi.n) } - } - else - { + } else { io.write_string(fi.writer, s, &fi.n) } @@ -1470,7 +1511,7 @@ fmt_pointer :: proc(fi: ^Info, p: rawptr, verb: rune) { u := u64(uintptr(p)) switch verb { case 'p', 'v', 'w': - if !fi.hash && verb == 'v' { + if !fi.hash { io.write_string(fi.writer, "0x", &fi.n) } _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER) @@ -1701,10 +1742,12 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') { et := runtime.type_info_base(info.elem) - if name != "" { - io.write_string(fi.writer, name, &fi.n) - } else { - reflect.write_type(fi.writer, type_info, &fi.n) + if verb != 'w' { + if name != "" { + io.write_string(fi.writer, name, &fi.n) + } else { + reflect.write_type(fi.writer, type_info, &fi.n) + } } io.write_byte(fi.writer, '{', &fi.n) defer io.write_byte(fi.writer, '}', &fi.n) @@ -1721,9 +1764,17 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') { } if is_enum { + enum_name: string + if ti_named, is_named := info.elem.variant.(runtime.Type_Info_Named); is_named { + enum_name = ti_named.name + } for ev, evi in e.values { v := u64(ev) if v == u64(i) { + if verb == 'w' { + io.write_string(fi.writer, enum_name, &fi.n) + io.write_byte(fi.writer, '.', &fi.n) + } io.write_string(fi.writer, e.names[evi], &fi.n) commas += 1 continue loop @@ -1805,12 +1856,12 @@ fmt_write_array :: proc(fi: ^Info, array_data: rawptr, count: int, elem_size: in // Returns: A boolean value indicating whether to continue processing the tag // @(private) -handle_tag :: proc(data: rawptr, info: reflect.Type_Info_Struct, idx: int, verb: ^rune, optional_len: ^int, use_nul_termination: ^bool) -> (do_continue: bool) { +handle_tag :: proc(state: ^Info_State, data: rawptr, info: reflect.Type_Info_Struct, idx: int, verb: ^rune, optional_len: ^int, use_nul_termination: ^bool) -> (do_continue: bool) { handle_optional_len :: proc(data: rawptr, info: reflect.Type_Info_Struct, field_name: string, optional_len: ^int) { if optional_len == nil { return } - for f, i in info.names { + for f, i in info.names[:info.field_count] { if f != field_name { continue } @@ -1822,45 +1873,83 @@ handle_tag :: proc(data: rawptr, info: reflect.Type_Info_Struct, idx: int, verb: break } } + tag := info.tags[idx] if vt, ok := reflect.struct_tag_lookup(reflect.Struct_Tag(tag), "fmt"); ok { value := strings.trim_space(string(vt)) switch value { - case "": return false + case "": return false case "-": return true } - r, w := utf8.decode_rune_in_string(value) - value = value[w:] - if value == "" || value[0] == ',' { - if verb^ == 'w' { - // TODO(bill): is this a good idea overriding that field tags if 'w' is used? - switch r { - case 's': r = 'q' - case: r = 'w' - } + + fi := state + + head, _, tail := strings.partition(value, ",") + + i := 0 + prefix_loop: for ; i < len(head); i += 1 { + switch head[i] { + case '+': + fi.plus = true + case '-': + fi.minus = true + fi.zero = false + case ' ': + fi.space = true + case '#': + fi.hash = true + case '0': + fi.zero = !fi.minus + case: + break prefix_loop } - verb^ = r - if len(value) > 0 && value[0] == ',' { - field_name := value[1:] - if field_name == "0" { - if use_nul_termination != nil { - use_nul_termination^ = true - } - } else { - switch r { - case 's', 'q': + } + + fi.width, i, fi.width_set = _parse_int(head, i) + if i < len(head) && head[i] == '.' { + i += 1 + prev_i := i + fi.prec, i, fi.prec_set = _parse_int(head, i) + if i == prev_i { + fi.prec = 0 + fi.prec_set = true + } + } + + r: rune + if i >= len(head) || head[i] == ' ' { + r = 'v' + } else { + r, _ = utf8.decode_rune_in_string(head[i:]) + } + if verb^ == 'w' { + // TODO(bill): is this a good idea overriding that field tags if 'w' is used? + switch r { + case 's': r = 'q' + case: r = 'w' + } + } + verb^ = r + if tail != "" { + field_name := tail + if field_name == "0" { + if use_nul_termination != nil { + use_nul_termination^ = true + } + } else { + switch r { + case 's', 'q': + handle_optional_len(data, info, field_name, optional_len) + case 'v', 'w': + #partial switch reflect.type_kind(info.types[idx].id) { + case .String, .Multi_Pointer, .Array, .Slice, .Dynamic_Array: handle_optional_len(data, info, field_name, optional_len) - case 'v', 'w': - #partial switch reflect.type_kind(info.types[idx].id) { - case .String, .Multi_Pointer, .Array, .Slice, .Dynamic_Array: - handle_optional_len(data, info, field_name, optional_len) - } } } } } } - return false + return } // Formats a struct for output, handling various struct types (e.g., SOA, raw unions) // @@ -1876,7 +1965,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St fmt_bad_verb(fi, the_verb) return } - if info.is_raw_union { + if .raw_union in info.flags { if type_name == "" { io.write_string(fi.writer, "(raw union)", &fi.n) } else { @@ -1900,11 +1989,13 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St // fi.hash = false; fi.indent += 1 - if !is_soa && hash { + is_empty := info.field_count == 0 + + if !is_soa && hash && !is_empty { io.write_byte(fi.writer, '\n', &fi.n) } defer { - if hash { + if !is_soa && hash && !is_empty { for _ in 0.. 0 { + for _ in 0.. 0 { io.write_string(fi.writer, "; ", &fi.n) } + if row > 0 { io.write_string(fi.writer, row_separator, &fi.n) } for col in 0.. 0 { io.write_string(fi.writer, ", ", &fi.n) } @@ -2505,7 +2605,7 @@ fmt_bit_field :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Bit field_count := -1 - for name, i in info.names { + for name, i in info.names[:info.field_count] { field_verb := verb if handle_bit_field_tag(v.data, info, i, &field_verb) { continue @@ -2526,8 +2626,11 @@ fmt_bit_field :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Bit bit_offset := info.bit_offsets[i] bit_size := info.bit_sizes[i] - value := read_bits(([^]byte)(v.data), bit_offset, bit_size) type := info.types[i] + value := read_bits(([^]byte)(v.data), bit_offset, bit_size) + if reflect.is_endian_big(type) { + value <<= u64(8*type.size) - u64(bit_size) + } if !reflect.is_unsigned(runtime.type_info_core(type)) { // Sign Extension @@ -2561,7 +2664,6 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { if _user_formatters != nil && !fi.ignore_user_formatters { formatter := _user_formatters[v.id] if formatter != nil { - fi.ignore_user_formatters = false if ok := formatter(fi, v, verb); !ok { fi.ignore_user_formatters = true fmt_bad_verb(fi, verb) @@ -2607,7 +2709,7 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { return } if fi.indirection_level < 1 { - fi.indirection_level += 1 + fi.indirection_level += 1 defer fi.indirection_level -= 1 io.write_byte(fi.writer, '&') fmt_value(fi, a, verb) @@ -2649,9 +2751,11 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { elem := runtime.type_info_base(info.elem) if elem != nil { if n, ok := fi.optional_len.?; ok { + fi.optional_len = nil fmt_array(fi, ptr, n, elem.size, elem, verb) return } else if fi.use_nul_termination { + fi.use_nul_termination = false fmt_array_nul_terminated(fi, ptr, -1, elem.size, elem, verb) return } @@ -2676,7 +2780,7 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { runtime.Type_Info_Dynamic_Array, runtime.Type_Info_Map: if fi.indirection_level < 1 { - fi.indirection_level += 1 + fi.indirection_level += 1 defer fi.indirection_level -= 1 io.write_byte(fi.writer, '&', &fi.n) fmt_value(fi, a, verb) @@ -2705,7 +2809,6 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { io.write_byte(fi.writer, '[' if verb != 'w' else '{', &fi.n) io.write_byte(fi.writer, '\n', &fi.n) defer { - io.write_byte(fi.writer, '\n', &fi.n) fmt_write_indent(fi) io.write_byte(fi.writer, ']' if verb != 'w' else '}', &fi.n) } @@ -2754,8 +2857,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { n := info.count ptr := v.data if ol, ok := fi.optional_len.?; ok { + fi.optional_len = nil n = min(n, ol) } else if fi.use_nul_termination { + fi.use_nul_termination = false fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb) return } @@ -2766,8 +2871,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { n := slice.len ptr := slice.data if ol, ok := fi.optional_len.?; ok { + fi.optional_len = nil n = min(n, ol) } else if fi.use_nul_termination { + fi.use_nul_termination = false fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb) return } @@ -2778,8 +2885,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { n := array.len ptr := array.data if ol, ok := fi.optional_len.?; ok { + fi.optional_len = nil n = min(n, ol) } else if fi.use_nul_termination { + fi.use_nul_termination = false fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb) return } @@ -2900,6 +3009,21 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { fmt_bit_field(fi, v, verb, info, "") } } +// This proc helps keep some of the code around whether or not to print an +// intermediate plus sign in complexes and quaternions more readable. +@(private) +_cq_should_print_intermediate_plus :: proc "contextless" (fi: ^Info, f: f64) -> bool { + if !fi.plus && f >= 0 { + #partial switch math.classify(f) { + case .Neg_Zero, .Inf: + // These two classes print their own signs. + return false + case: + return true + } + } + return false +} // Formats a complex number based on the given formatting verb // // Inputs: @@ -2913,7 +3037,7 @@ fmt_complex :: proc(fi: ^Info, c: complex128, bits: int, verb: rune) { case 'f', 'F', 'v', 'h', 'H', 'w': r, i := real(c), imag(c) fmt_float(fi, r, bits/2, verb) - if !fi.plus && i >= 0 { + if _cq_should_print_intermediate_plus(fi, i) { io.write_rune(fi.writer, '+', &fi.n) } fmt_float(fi, i, bits/2, verb) @@ -2939,19 +3063,19 @@ fmt_quaternion :: proc(fi: ^Info, q: quaternion256, bits: int, verb: rune) { fmt_float(fi, r, bits/4, verb) - if !fi.plus && i >= 0 { + if _cq_should_print_intermediate_plus(fi, i) { io.write_rune(fi.writer, '+', &fi.n) } fmt_float(fi, i, bits/4, verb) io.write_rune(fi.writer, 'i', &fi.n) - if !fi.plus && j >= 0 { + if _cq_should_print_intermediate_plus(fi, j) { io.write_rune(fi.writer, '+', &fi.n) } fmt_float(fi, j, bits/4, verb) io.write_rune(fi.writer, 'j', &fi.n) - if !fi.plus && k >= 0 { + if _cq_should_print_intermediate_plus(fi, k) { io.write_rune(fi.writer, '+', &fi.n) } fmt_float(fi, k, bits/4, verb) diff --git a/core/fmt/fmt_js.odin b/core/fmt/fmt_js.odin index a0a890a9a..acf218eb5 100644 --- a/core/fmt/fmt_js.odin +++ b/core/fmt/fmt_js.odin @@ -1,7 +1,9 @@ //+build js package fmt +import "core:bufio" import "core:io" +import "core:os" foreign import "odin_env" @@ -31,6 +33,55 @@ stderr := io.Writer{ data = rawptr(uintptr(2)), } +@(private="file") +fd_to_writer :: proc(fd: os.Handle, loc := #caller_location) -> io.Writer { + switch fd { + case 1: return stdout + case 2: return stderr + case: panic("`fmt.fprint` variant called with invalid file descriptor for JS, only 1 (stdout) and 2 (stderr) are supported", loc) + } +} + +// fprint formats using the default print settings and writes to fd +fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { + buf: [1024]byte + b: bufio.Writer + defer bufio.writer_flush(&b) + + bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:]) + w := bufio.writer_to_writer(&b) + return wprint(w, ..args, sep=sep, flush=flush) +} + +// fprintln formats using the default print settings and writes to fd +fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { + buf: [1024]byte + b: bufio.Writer + defer bufio.writer_flush(&b) + + bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:]) + + w := bufio.writer_to_writer(&b) + return wprintln(w, ..args, sep=sep, flush=flush) +} + +// fprintf formats according to the specified format string and writes to fd +fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline := false, loc := #caller_location) -> int { + buf: [1024]byte + b: bufio.Writer + defer bufio.writer_flush(&b) + + bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:]) + + w := bufio.writer_to_writer(&b) + return wprintf(w, fmt, ..args, flush=flush, newline=newline) +} + +// fprintfln formats according to the specified format string and writes to fd, followed by a newline. +fprintfln :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, loc := #caller_location) -> int { + return fprintf(fd, fmt, ..args, flush=flush, newline=true, loc=loc) +} + // print formats using the default print settings and writes to stdout print :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stdout, args=args, sep=sep, flush=flush) } // println formats using the default print settings and writes to stdout diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin index a403dcd65..9de0d43be 100644 --- a/core/fmt/fmt_os.odin +++ b/core/fmt/fmt_os.odin @@ -1,5 +1,6 @@ //+build !freestanding //+build !js +//+build !orca package fmt import "base:runtime" diff --git a/core/hash/crc.odin b/core/hash/crc.odin index cb3e36881..68b8f5369 100644 --- a/core/hash/crc.odin +++ b/core/hash/crc.odin @@ -1,6 +1,6 @@ package hash -@(optimization_mode="speed") +@(optimization_mode="favor_size") crc64_ecma_182 :: proc "contextless" (data: []byte, seed := u64(0)) -> (result: u64) #no_bounds_check { result = seed #no_bounds_check for b in data { @@ -14,7 +14,7 @@ crc64_ecma_182 :: proc "contextless" (data: []byte, seed := u64(0)) -> (result: bit-reversed, with one's complement pre and post processing. Based on Mark Adler's v1.4 implementation in C under the ZLIB license. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") crc64_xz :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_check { data := data result := ~u64le(seed) @@ -52,7 +52,7 @@ crc64_xz :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_ /* Generator polynomial: x^64 + x^4 + x^3 + x + 1 */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") crc64_iso_3306 :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_check { result := seed @@ -738,4 +738,4 @@ crc64_iso_3306_inverse :: proc "contextless" (data: []byte, seed := u64(0)) -> u 0x9fc0, 0x9e70, 0x9ca0, 0x9d10, 0x9480, 0x9530, 0x97e0, 0x9650, 0x9240, 0x93f0, 0x9120, 0x9090, -} \ No newline at end of file +} diff --git a/core/hash/crc32.odin b/core/hash/crc32.odin index 5dde467a7..a7f68207e 100644 --- a/core/hash/crc32.odin +++ b/core/hash/crc32.odin @@ -2,7 +2,7 @@ package hash import "base:intrinsics" -@(optimization_mode="speed") +@(optimization_mode="favor_size") crc32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 #no_bounds_check { crc := ~seed buffer := raw_data(data) diff --git a/core/hash/hash.odin b/core/hash/hash.odin index fb170bfe4..45f524d8a 100644 --- a/core/hash/hash.odin +++ b/core/hash/hash.odin @@ -3,7 +3,7 @@ package hash import "core:mem" import "base:intrinsics" -@(optimization_mode="speed") +@(optimization_mode="favor_size") adler32 :: proc "contextless" (data: []byte, seed := u32(1)) -> u32 #no_bounds_check { ADLER_CONST :: 65521 @@ -46,7 +46,7 @@ adler32 :: proc "contextless" (data: []byte, seed := u32(1)) -> u32 #no_bounds_c return (u32(b) << 16) | u32(a) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") djb2 :: proc "contextless" (data: []byte, seed := u32(5381)) -> u32 { hash: u32 = seed for b in data { @@ -73,7 +73,7 @@ djbx33a :: proc "contextless" (data: []byte, seed := u32(5381)) -> (result: [16] } // If you have a choice, prefer fnv32a -@(optimization_mode="speed") +@(optimization_mode="favor_size") fnv32_no_a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 { h: u32 = seed for b in data { @@ -86,7 +86,7 @@ fnv32 :: fnv32_no_a // NOTE(bill): Not a fan of these aliases but seems necessar fnv64 :: fnv64_no_a // NOTE(bill): Not a fan of these aliases but seems necessary // If you have a choice, prefer fnv64a -@(optimization_mode="speed") +@(optimization_mode="favor_size") fnv64_no_a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { h: u64 = seed for b in data { @@ -94,7 +94,7 @@ fnv64_no_a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) } return h } -@(optimization_mode="speed") +@(optimization_mode="favor_size") fnv32a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 { h: u32 = seed for b in data { @@ -103,7 +103,7 @@ fnv32a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 { return h } -@(optimization_mode="speed") +@(optimization_mode="favor_size") fnv64a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { h: u64 = seed for b in data { @@ -112,7 +112,7 @@ fnv64a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) -> return h } -@(optimization_mode="speed") +@(optimization_mode="favor_size") jenkins :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { hash: u32 = seed for b in data { @@ -126,7 +126,7 @@ jenkins :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { return hash } -@(optimization_mode="speed") +@(optimization_mode="favor_size") murmur32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { c1_32: u32 : 0xcc9e2d51 c2_32: u32 : 0x1b873593 @@ -177,7 +177,7 @@ murmur32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { } // See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L96 -@(optimization_mode="speed") +@(optimization_mode="favor_size") murmur64a :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 { m :: 0xc6a4a7935bd1e995 r :: 47 @@ -218,7 +218,7 @@ murmur64a :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 { } // See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L140 -@(optimization_mode="speed") +@(optimization_mode="favor_size") murmur64b :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 { m :: 0x5bd1e995 r :: 24 @@ -286,7 +286,7 @@ murmur64b :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 { return u64(h1)<<32 | u64(h2) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") sdbm :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { hash: u32 = seed for b in data { diff --git a/core/hash/xxhash/common.odin b/core/hash/xxhash/common.odin index faf88e0d4..bbeb60db3 100644 --- a/core/hash/xxhash/common.odin +++ b/core/hash/xxhash/common.odin @@ -67,17 +67,17 @@ when !XXH_DISABLE_PREFETCH { } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH_rotl32 :: #force_inline proc(x, r: u32) -> (res: u32) { return ((x << r) | (x >> (32 - r))) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH_rotl64 :: #force_inline proc(x, r: u64) -> (res: u64) { return ((x << r) | (x >> (64 - r))) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH32_read32 :: #force_inline proc(buf: []u8, alignment := Alignment.Unaligned) -> (res: u32) { if XXH_FORCE_MEMORY_ACCESS == 2 || alignment == .Aligned { #no_bounds_check b := (^u32le)(&buf[0])^ @@ -89,7 +89,7 @@ XXH32_read32 :: #force_inline proc(buf: []u8, alignment := Alignment.Unaligned) } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_read64 :: #force_inline proc(buf: []u8, alignment := Alignment.Unaligned) -> (res: u64) { if XXH_FORCE_MEMORY_ACCESS == 2 || alignment == .Aligned { #no_bounds_check b := (^u64le)(&buf[0])^ @@ -99,4 +99,4 @@ XXH64_read64 :: #force_inline proc(buf: []u8, alignment := Alignment.Unaligned) mem_copy(&b, raw_data(buf[:]), 8) return u64(b) } -} \ No newline at end of file +} diff --git a/core/hash/xxhash/xxhash_3.odin b/core/hash/xxhash/xxhash_3.odin index 611f4dc9f..9e159260b 100644 --- a/core/hash/xxhash/xxhash_3.odin +++ b/core/hash/xxhash/xxhash_3.odin @@ -111,13 +111,13 @@ XXH128_canonical :: struct { @param lhs, rhs The 64-bit integers to multiply @return The low 64 bits of the product XOR'd by the high 64 bits. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH_mul_64_to_128_fold_64 :: #force_inline proc(lhs, rhs: xxh_u64) -> (res: xxh_u64) { t := u128(lhs) * u128(rhs) return u64(t & 0xFFFFFFFFFFFFFFFF) ~ u64(t >> 64) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH_xorshift_64 :: #force_inline proc(v: xxh_u64, #any_int shift: uint) -> (res: xxh_u64) { return v ~ (v >> shift) } @@ -125,7 +125,7 @@ XXH_xorshift_64 :: #force_inline proc(v: xxh_u64, #any_int shift: uint) -> (res: /* This is a fast avalanche stage, suitable when input bits are already partially mixed */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_avalanche :: #force_inline proc(h64: xxh_u64) -> (res: xxh_u64) { res = XXH_xorshift_64(h64, 37) res *= 0x165667919E3779F9 @@ -137,7 +137,7 @@ XXH3_avalanche :: #force_inline proc(h64: xxh_u64) -> (res: xxh_u64) { This is a stronger avalanche, inspired by Pelle Evensen's rrmxmx preferable when input has not been previously mixed */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_rrmxmx :: #force_inline proc(h64, length: xxh_u64) -> (res: xxh_u64) { /* this mix is inspired by Pelle Evensen's rrmxmx */ res = h64 @@ -166,7 +166,7 @@ XXH3_rrmxmx :: #force_inline proc(h64, length: xxh_u64) -> (res: xxh_u64) { fast for a _128-bit_ hash on 32-bit (it usually clears XXH64). */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_1to3_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) { /* A doubled version of 1to3_64b with different constants. */ length := len(input) @@ -190,7 +190,7 @@ XXH3_len_1to3_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u6 } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_4to8_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) { length := len(input) seed := seed @@ -219,7 +219,7 @@ XXH3_len_4to8_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u6 } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_9to16_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) { length := len(input) @@ -261,7 +261,7 @@ XXH3_len_9to16_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u /* Assumption: `secret` size is >= XXH3_SECRET_SIZE_MIN */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_0to16_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) { length := len(input) @@ -279,7 +279,7 @@ XXH3_len_0to16_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u /* A bit slower than XXH3_mix16B, but handles multiply by zero better. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH128_mix32B :: #force_inline proc(acc: xxh_u128, input_1: []u8, input_2: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) { acc128 := XXH128_hash_t{ h = acc, @@ -293,7 +293,7 @@ XXH128_mix32B :: #force_inline proc(acc: xxh_u128, input_1: []u8, input_2: []u8, } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_17to128_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) { length := len(input) @@ -323,7 +323,7 @@ XXH3_len_17to128_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh unreachable() } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_129to240_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) { length := len(input) @@ -379,7 +379,7 @@ XXH3_INIT_ACC :: [XXH_ACC_NB]xxh_u64{ XXH_SECRET_MERGEACCS_START :: 11 -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_128b_internal :: #force_inline proc( input: []u8, secret: []u8, @@ -407,7 +407,7 @@ XXH3_hashLong_128b_internal :: #force_inline proc( /* * It's important for performance that XXH3_hashLong is not inlined. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_128b_default :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash) { return XXH3_hashLong_128b_internal(input, XXH3_kSecret[:], XXH3_accumulate_512, XXH3_scramble_accumulator) } @@ -415,12 +415,12 @@ XXH3_hashLong_128b_default :: #force_no_inline proc(input: []u8, seed: xxh_u64, /* * It's important for performance that XXH3_hashLong is not inlined. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_128b_withSecret :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash) { return XXH3_hashLong_128b_internal(input, secret, XXH3_accumulate_512, XXH3_scramble_accumulator) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_128b_withSeed_internal :: #force_inline proc( input: []u8, seed: xxh_u64, secret: []u8, f_acc512: XXH3_accumulate_512_f, @@ -441,14 +441,14 @@ XXH3_hashLong_128b_withSeed_internal :: #force_inline proc( /* * It's important for performance that XXH3_hashLong is not inlined. */ - @(optimization_mode="speed") + @(optimization_mode="favor_size") XXH3_hashLong_128b_withSeed :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash) { return XXH3_hashLong_128b_withSeed_internal(input, seed, secret, XXH3_accumulate_512, XXH3_scramble_accumulator , XXH3_init_custom_secret) } XXH3_hashLong128_f :: #type proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash) -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_128bits_internal :: #force_inline proc( input: []u8, seed: xxh_u64, secret: []u8, f_hl128: XXH3_hashLong128_f) -> (res: XXH3_128_hash) { @@ -474,17 +474,17 @@ XXH3_128bits_internal :: #force_inline proc( } /* === Public XXH128 API === */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_128_default :: proc(input: []u8) -> (hash: XXH3_128_hash) { return XXH3_128bits_internal(input, 0, XXH3_kSecret[:], XXH3_hashLong_128b_withSeed) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_128_with_seed :: proc(input: []u8, seed: xxh_u64) -> (hash: XXH3_128_hash) { return XXH3_128bits_internal(input, seed, XXH3_kSecret[:], XXH3_hashLong_128b_withSeed) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_128_with_secret :: proc(input: []u8, secret: []u8) -> (hash: XXH3_128_hash) { return XXH3_128bits_internal(input, 0, secret, XXH3_hashLong_128b_withSecret) } @@ -519,7 +519,7 @@ XXH3_128 :: proc { XXH3_128_default, XXH3_128_with_seed, XXH3_128_with_secret } The XOR mixing hides individual parts of the secret and increases entropy. This adds an extra layer of strength for custom secrets. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_1to3_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) { length := u32(len(input)) assert(input != nil) @@ -542,7 +542,7 @@ XXH3_len_1to3_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64 } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_4to8_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) { length := u32(len(input)) assert(input != nil) @@ -562,7 +562,7 @@ XXH3_len_4to8_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64 } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_9to16_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) { length := u64(len(input)) assert(input != nil) @@ -579,7 +579,7 @@ XXH3_len_9to16_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u6 } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_0to16_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) { length := u64(len(input)) assert(input != nil) @@ -621,7 +621,7 @@ XXH3_len_0to16_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u6 by this, although it is always a good idea to use a proper seed if you care about strength. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_mix16B :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) { input_lo := XXH64_read64(input[0:]) input_hi := XXH64_read64(input[8:]) @@ -632,7 +632,7 @@ XXH3_mix16B :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> ( } /* For mid range keys, XXH3 uses a Mum-hash variant. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_17to128_64b :: proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) { assert(len(secret) >= XXH3_SECRET_SIZE_MIN) length := len(input) @@ -665,7 +665,7 @@ XXH3_MIDSIZE_MAX :: 240 XXH3_MIDSIZE_STARTOFFSET :: 3 XXH3_MIDSIZE_LASTOFFSET :: 17 -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_129to240_64b :: proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) { assert(len(secret) >= XXH3_SECRET_SIZE_MIN) length := len(input) @@ -699,7 +699,7 @@ XXH_SECRET_CONSUME_RATE :: 8 /* nb of secret bytes consumed at each accumulatio XXH_ACC_NB :: (XXH_STRIPE_LEN / size_of(xxh_u64)) XXH_SECRET_LASTACC_START :: 7 /* not aligned on 8, last secret is different from acc & scrambler */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH_writeLE64 :: #force_inline proc(dst: []u8, v64: u64le) { v := v64 mem_copy(raw_data(dst), &v, size_of(v64)) @@ -737,7 +737,7 @@ XXH3_scramble_accumulator : XXH3_scramble_accumulator_f = XXH3_scramble_accumula XXH3_init_custom_secret : XXH3_init_custom_secret_f = XXH3_init_custom_secret_scalar /* scalar variants - universal */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_accumulate_512_scalar :: #force_inline proc(acc: []xxh_u64, input: []u8, secret: []u8) { xacc := acc /* presumed aligned */ xinput := input /* no alignment restriction */ @@ -754,7 +754,7 @@ XXH3_accumulate_512_scalar :: #force_inline proc(acc: []xxh_u64, input: []u8, se } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_scramble_accumulator_scalar :: #force_inline proc(acc: []xxh_u64, secret: []u8) { xacc := acc /* presumed aligned */ xsecret := secret /* no alignment restriction */ @@ -771,7 +771,7 @@ XXH3_scramble_accumulator_scalar :: #force_inline proc(acc: []xxh_u64, secret: [ } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_init_custom_secret_scalar :: #force_inline proc(custom_secret: []u8, seed64: xxh_u64) { #assert((XXH_SECRET_DEFAULT_SIZE & 15) == 0) @@ -791,7 +791,7 @@ XXH_PREFETCH_DIST :: 320 * Loops over XXH3_accumulate_512(). * Assumption: nbStripes will not overflow the secret size */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_accumulate :: #force_inline proc( acc: []xxh_u64, input: []u8, secret: []u8, nbStripes: uint, f_acc512: XXH3_accumulate_512_f) { @@ -804,7 +804,7 @@ XXH3_accumulate :: #force_inline proc( } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_internal_loop :: #force_inline proc(acc: []xxh_u64, input: []u8, secret: []u8, f_acc512: XXH3_accumulate_512_f, f_scramble: XXH3_scramble_accumulator_f) { @@ -833,14 +833,14 @@ XXH3_hashLong_internal_loop :: #force_inline proc(acc: []xxh_u64, input: []u8, s } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_mix2Accs :: #force_inline proc(acc: []xxh_u64, secret: []u8) -> (res: xxh_u64) { return XXH_mul_64_to_128_fold_64( acc[0] ~ XXH64_read64(secret), acc[1] ~ XXH64_read64(secret[8:])) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_mergeAccs :: #force_inline proc(acc: []xxh_u64, secret: []u8, start: xxh_u64) -> (res: xxh_u64) { result64 := start #no_bounds_check for i := 0; i < 4; i += 1 { @@ -849,7 +849,7 @@ XXH3_mergeAccs :: #force_inline proc(acc: []xxh_u64, secret: []u8, start: xxh_u6 return XXH3_avalanche(result64) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_64b_internal :: #force_inline proc(input: []u8, secret: []u8, f_acc512: XXH3_accumulate_512_f, f_scramble: XXH3_scramble_accumulator_f) -> (hash: xxh_u64) { @@ -868,7 +868,7 @@ XXH3_hashLong_64b_internal :: #force_inline proc(input: []u8, secret: []u8, /* It's important for performance that XXH3_hashLong is not inlined. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_64b_withSecret :: #force_no_inline proc(input: []u8, seed64: xxh_u64, secret: []u8) -> (hash: xxh_u64) { return XXH3_hashLong_64b_internal(input, secret, XXH3_accumulate_512, XXH3_scramble_accumulator) } @@ -880,7 +880,7 @@ XXH3_hashLong_64b_withSecret :: #force_no_inline proc(input: []u8, seed64: xxh_u This variant enforces that the compiler can detect that, and uses this opportunity to streamline the generated code for better performance. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_64b_default :: #force_no_inline proc(input: []u8, seed64: xxh_u64, secret: []u8) -> (hash: xxh_u64) { return XXH3_hashLong_64b_internal(input, XXH3_kSecret[:], XXH3_accumulate_512, XXH3_scramble_accumulator) } @@ -896,26 +896,27 @@ XXH3_hashLong_64b_default :: #force_no_inline proc(input: []u8, seed64: xxh_u64, It's important for performance that XXH3_hashLong is not inlined. Not sure why (uop cache maybe?), but the difference is large and easily measurable. */ -@(optimization_mode="speed") -XXH3_hashLong_64b_withSeed_internal :: #force_no_inline proc(input: []u8, - seed: xxh_u64, - f_acc512: XXH3_accumulate_512_f, - f_scramble: XXH3_scramble_accumulator_f, - f_init_sec: XXH3_init_custom_secret_f) -> (hash: xxh_u64) { +@(optimization_mode="favor_size") +XXH3_hashLong_64b_withSeed_internal :: #force_no_inline proc( + input: []u8, + seed: xxh_u64, + f_acc512: XXH3_accumulate_512_f, + f_scramble: XXH3_scramble_accumulator_f, + f_init_sec: XXH3_init_custom_secret_f, +) -> (hash: xxh_u64) { if seed == 0 { return XXH3_hashLong_64b_internal(input, XXH3_kSecret[:], f_acc512, f_scramble) } - { - secret: [XXH_SECRET_DEFAULT_SIZE]u8 - f_init_sec(secret[:], seed) - return XXH3_hashLong_64b_internal(input, secret[:], f_acc512, f_scramble) - } + + secret: [XXH_SECRET_DEFAULT_SIZE]u8 + f_init_sec(secret[:], seed) + return XXH3_hashLong_64b_internal(input, secret[:], f_acc512, f_scramble) } /* It's important for performance that XXH3_hashLong is not inlined. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_64b_withSeed :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (hash: xxh_u64) { return XXH3_hashLong_64b_withSeed_internal(input, seed, XXH3_accumulate_512, XXH3_scramble_accumulator, XXH3_init_custom_secret) } @@ -923,7 +924,7 @@ XXH3_hashLong_64b_withSeed :: #force_no_inline proc(input: []u8, seed: xxh_u64, XXH3_hashLong64_f :: #type proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: xxh_u64) -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_64bits_internal :: proc(input: []u8, seed: xxh_u64, secret: []u8, f_hashLong: XXH3_hashLong64_f) -> (hash: xxh_u64) { assert(len(secret) >= XXH3_SECRET_SIZE_MIN) /* @@ -943,19 +944,19 @@ XXH3_64bits_internal :: proc(input: []u8, seed: xxh_u64, secret: []u8, f_hashLon } /* === Public entry point === */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_64_default :: proc(input: []u8) -> (hash: xxh_u64) { return XXH3_64bits_internal(input, 0, XXH3_kSecret[:], XXH3_hashLong_64b_default) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_64_with_seed :: proc(input: []u8, seed: xxh_u64) -> (hash: xxh_u64) { return XXH3_64bits_internal(input, seed, XXH3_kSecret[:], XXH3_hashLong_64b_withSeed) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_64_with_secret :: proc(input, secret: []u8) -> (hash: xxh_u64) { return XXH3_64bits_internal(input, 0, secret, XXH3_hashLong_64b_withSecret) } -XXH3_64 :: proc { XXH3_64_default, XXH3_64_with_seed, XXH3_64_with_secret } \ No newline at end of file +XXH3_64 :: proc { XXH3_64_default, XXH3_64_with_seed, XXH3_64_with_secret } diff --git a/core/hash/xxhash/xxhash_32.odin b/core/hash/xxhash/xxhash_32.odin index b0dea305e..3ea1c3cf2 100644 --- a/core/hash/xxhash/xxhash_32.odin +++ b/core/hash/xxhash/xxhash_32.odin @@ -40,7 +40,7 @@ XXH_PRIME32_3 :: 0xC2B2AE3D /*!< 0b11000010101100101010111000111101 */ XXH_PRIME32_4 :: 0x27D4EB2F /*!< 0b00100111110101001110101100101111 */ XXH_PRIME32_5 :: 0x165667B1 /*!< 0b00010110010101100110011110110001 */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH32_round :: #force_inline proc(seed, input: XXH32_hash) -> (res: XXH32_hash) { seed := seed @@ -53,7 +53,7 @@ XXH32_round :: #force_inline proc(seed, input: XXH32_hash) -> (res: XXH32_hash) /* Mix all bits */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH32_avalanche :: #force_inline proc(h32: u32) -> (res: u32) { h32 := h32 @@ -65,7 +65,7 @@ XXH32_avalanche :: #force_inline proc(h32: u32) -> (res: u32) { return h32 } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH32_finalize :: #force_inline proc(h32: u32, buf: []u8, alignment: Alignment) -> (res: u32) { process_1 :: #force_inline proc(h32: u32, buf: []u8) -> (h32_res: u32, buf_res: []u8) { #no_bounds_check b := u32(buf[0]) @@ -143,7 +143,7 @@ XXH32_finalize :: #force_inline proc(h32: u32, buf: []u8, alignment: Alignment) unreachable() } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH32_endian_align :: #force_inline proc(input: []u8, seed := XXH32_DEFAULT_SEED, alignment: Alignment) -> (res: XXH32_hash) { buf := input length := len(input) @@ -318,4 +318,4 @@ XXH32_canonical_from_hash :: proc(hash: XXH32_hash) -> (canonical: XXH32_canonic XXH32_hash_from_canonical :: proc(canonical: ^XXH32_canonical) -> (hash: XXH32_hash) { h := (^u32be)(&canonical.digest)^ return XXH32_hash(h) -} \ No newline at end of file +} diff --git a/core/hash/xxhash/xxhash_64.odin b/core/hash/xxhash/xxhash_64.odin index b274da374..3b24f20a1 100644 --- a/core/hash/xxhash/xxhash_64.odin +++ b/core/hash/xxhash/xxhash_64.odin @@ -40,7 +40,7 @@ XXH_PRIME64_3 :: 0x165667B19E3779F9 /*!< 0b0001011001010110011001111011000110011 XXH_PRIME64_4 :: 0x85EBCA77C2B2AE63 /*!< 0b1000010111101011110010100111011111000010101100101010111001100011 */ XXH_PRIME64_5 :: 0x27D4EB2F165667C5 /*!< 0b0010011111010100111010110010111100010110010101100110011111000101 */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_round :: proc(acc, input: xxh_u64) -> (res: xxh_u64) { acc := acc @@ -50,14 +50,14 @@ XXH64_round :: proc(acc, input: xxh_u64) -> (res: xxh_u64) { return acc } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_mergeRound :: proc(acc, val: xxh_u64) -> (res: xxh_u64) { res = acc ~ XXH64_round(0, val) res = res * XXH_PRIME64_1 + XXH_PRIME64_4 return res } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_avalanche :: proc(h64: xxh_u64) -> (res: xxh_u64) { res = h64 res ~= res >> 33 @@ -68,7 +68,7 @@ XXH64_avalanche :: proc(h64: xxh_u64) -> (res: xxh_u64) { return res } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_finalize :: proc(h64: xxh_u64, buf: []u8, alignment: Alignment) -> (res: xxh_u64) { buf := buf length := len(buf) & 31 @@ -100,7 +100,7 @@ XXH64_finalize :: proc(h64: xxh_u64, buf: []u8, alignment: Alignment) -> (res: x return XXH64_avalanche(res) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_endian_align :: proc(input: []u8, seed := XXH64_DEFAULT_SEED, alignment := Alignment.Unaligned) -> (res: xxh_u64) { buf := input length := len(buf) @@ -191,7 +191,7 @@ XXH64_reset_state :: proc(state_ptr: ^XXH64_state, seed := XXH64_DEFAULT_SEED) - return .None } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_update :: proc(state: ^XXH64_state, input: []u8) -> (err: Error) { buf := input length := len(buf) @@ -245,7 +245,7 @@ XXH64_update :: proc(state: ^XXH64_state, input: []u8) -> (err: Error) { return .None } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_digest :: proc(state: ^XXH64_state) -> (res: XXH64_hash) { if state.total_len >= 32 { v1 := state.v1 @@ -292,4 +292,4 @@ XXH64_canonical_from_hash :: proc(hash: XXH64_hash) -> (canonical: XXH64_canonic XXH64_hash_from_canonical :: proc(canonical: ^XXH64_canonical) -> (hash: XXH64_hash) { h := (^u64be)(&canonical.digest)^ return XXH64_hash(h) -} \ No newline at end of file +} diff --git a/core/image/bmp/bmp.odin b/core/image/bmp/bmp.odin new file mode 100644 index 000000000..057c2ffa0 --- /dev/null +++ b/core/image/bmp/bmp.odin @@ -0,0 +1,746 @@ +// package bmp implements a Microsoft BMP image reader +package core_image_bmp + +import "core:image" +import "core:bytes" +import "core:compress" +import "core:mem" +import "base:intrinsics" +import "base:runtime" + +Error :: image.Error +Image :: image.Image +Options :: image.Options + +RGB_Pixel :: image.RGB_Pixel +RGBA_Pixel :: image.RGBA_Pixel + +FILE_HEADER_SIZE :: 14 +INFO_STUB_SIZE :: FILE_HEADER_SIZE + size_of(image.BMP_Version) + +save_to_buffer :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + if img == nil { + return .Invalid_Input_Image + } + + if output == nil { + return .Invalid_Output + } + + pixels := img.width * img.height + if pixels == 0 || pixels > image.MAX_DIMENSIONS { + return .Invalid_Input_Image + } + + // While the BMP spec (and our loader) support more fanciful image types, + // `bmp.save` supports only 3 and 4 channel images with a bit depth of 8. + if img.depth != 8 || img.channels < 3 || img.channels > 4 { + return .Invalid_Input_Image + } + + if img.channels * pixels != len(img.pixels.buf) { + return .Invalid_Input_Image + } + + // Calculate and allocate size. + header_size := u32le(image.BMP_Version.V3) + total_header_size := header_size + 14 // file header = 14 + pixel_count_bytes := u32le(align4(img.width * img.channels) * img.height) + + header := image.BMP_Header{ + // File header + magic = .Bitmap, + size = total_header_size + pixel_count_bytes, + _res1 = 0, + _res2 = 0, + pixel_offset = total_header_size, + // V3 + info_size = .V3, + width = i32le(img.width), + height = i32le(img.height), + planes = 1, + bpp = u16le(8 * img.channels), + compression = .RGB, + image_size = pixel_count_bytes, + pels_per_meter = {2835, 2835}, // 72 DPI + colors_used = 0, + colors_important = 0, + } + written := 0 + + if resize(&output.buf, int(header.size)) != nil { + return .Unable_To_Allocate_Or_Resize + } + + header_bytes := transmute([size_of(image.BMP_Header)]u8)header + written += int(total_header_size) + copy(output.buf[:], header_bytes[:written]) + + switch img.channels { + case 3: + row_bytes := img.width * img.channels + row_padded := align4(row_bytes) + pixels := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:]) + for y in 0.. (img: ^Image, err: Error) { + ctx := &compress.Context_Memory_Input{ + input_data = data, + } + + img, err = load_from_context(ctx, options, allocator) + return img, err +} + +@(optimization_mode="favor_size") +load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + context.allocator = allocator + options := options + + // For compress.read_slice(), until that's rewritten to not use temp allocator + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + if .info in options { + options += {.return_metadata, .do_not_decompress_image} + options -= {.info} + } + + if .return_header in options && .return_metadata in options { + options -= {.return_header} + } + + info_buf: [size_of(image.BMP_Header)]u8 + + // Read file header (14) + info size (4) + stub_data := compress.read_slice(ctx, INFO_STUB_SIZE) or_return + copy(info_buf[:], stub_data[:]) + stub_info := transmute(image.BMP_Header)info_buf + + if stub_info.magic != .Bitmap { + for v in image.BMP_Magic { + if stub_info.magic == v { + return img, .Unsupported_OS2_File + } + } + return img, .Invalid_Signature + } + + info: image.BMP_Header + switch stub_info.info_size { + case .OS2_v1: + // Read the remainder of the header + os2_data := compress.read_data(ctx, image.OS2_Header) or_return + + info = transmute(image.BMP_Header)info_buf + info.width = i32le(os2_data.width) + info.height = i32le(os2_data.height) + info.planes = os2_data.planes + info.bpp = os2_data.bpp + + switch info.bpp { + case 1, 4, 8, 24: + case: + return img, .Unsupported_BPP + } + + case .ABBR_16 ..= .V5: + // Sizes include V3, V4, V5 and OS2v2 outright, but can also handle truncated headers. + // Sometimes called BITMAPV2INFOHEADER or BITMAPV3INFOHEADER. + // Let's just try to process it. + + to_read := int(stub_info.info_size) - size_of(image.BMP_Version) + info_data := compress.read_slice(ctx, to_read) or_return + copy(info_buf[INFO_STUB_SIZE:], info_data[:]) + + // Update info struct with the rest of the data we read + info = transmute(image.BMP_Header)info_buf + + case: + return img, .Unsupported_BMP_Version + } + + /* TODO(Jeroen): Add a "strict" option to catch these non-issues that violate spec? + if info.planes != 1 { + return img, .Invalid_Planes_Value + } + */ + + if img == nil { + img = new(Image) + } + img.which = .BMP + + img.metadata = new_clone(image.BMP_Info{ + info = info, + }) + + img.width = abs(int(info.width)) + img.height = abs(int(info.height)) + img.channels = 3 + img.depth = 8 + + if img.width == 0 || img.height == 0 { + return img, .Invalid_Image_Dimensions + } + + total_pixels := abs(img.width * img.height) + if total_pixels > image.MAX_DIMENSIONS { + return img, .Image_Dimensions_Too_Large + } + + // TODO(Jeroen): Handle RGBA. + switch info.compression { + case .Bit_Fields, .Alpha_Bit_Fields: + switch info.bpp { + case 16, 32: + make_output(img, allocator) or_return + decode_rgb(ctx, img, info, allocator) or_return + case: + if is_os2(info.info_size) { + return img, .Unsupported_Compression + } + return img, .Unsupported_BPP + } + case .RGB: + make_output(img, allocator) or_return + decode_rgb(ctx, img, info, allocator) or_return + case .RLE4, .RLE8: + make_output(img, allocator) or_return + decode_rle(ctx, img, info, allocator) or_return + case .CMYK, .CMYK_RLE4, .CMYK_RLE8: fallthrough + case .PNG, .JPEG: fallthrough + case: return img, .Unsupported_Compression + } + + // Flipped vertically + if info.height < 0 { + pixels := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:]) + for y in 0.. (res: bool) { + #partial switch version { + case .OS2_v1, .OS2_v2: return true + case: return false + } +} + +make_output :: proc(img: ^Image, allocator := context.allocator) -> (err: Error) { + assert(img != nil) + bytes_needed := img.channels * img.height * img.width + img.pixels.buf = make([dynamic]u8, bytes_needed, allocator) + if len(img.pixels.buf) != bytes_needed { + return .Unable_To_Allocate_Or_Resize + } + return +} + +write :: proc(img: ^Image, x, y: int, pix: RGB_Pixel) -> (err: Error) { + if y >= img.height || x >= img.width { + return .Corrupt + } + out := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:]) + assert(img.height >= 1 && img.width >= 1) + out[(img.height - y - 1) * img.width + x] = pix + return +} + +Bitmask :: struct { + mask: [4]u32le `fmt:"b"`, + shift: [4]u32le, + bits: [4]u32le, +} + +read_or_make_bit_masks :: proc(ctx: ^$C, info: image.BMP_Header) -> (res: Bitmask, read: int, err: Error) { + ctz :: intrinsics.count_trailing_zeros + c1s :: intrinsics.count_ones + + #partial switch info.compression { + case .RGB: + switch info.bpp { + case 16: + return { + mask = {31 << 10, 31 << 5, 31, 0}, + shift = { 10, 5, 0, 0}, + bits = { 5, 5, 5, 0}, + }, int(4 * info.colors_used), nil + + case 32: + return { + mask = {255 << 16, 255 << 8, 255, 255 << 24}, + shift = { 16, 8, 0, 24}, + bits = { 8, 8, 8, 8}, + }, int(4 * info.colors_used), nil + + case: return {}, 0, .Unsupported_BPP + } + case .Bit_Fields, .Alpha_Bit_Fields: + bf := info.masks + alpha_mask := false + bit_count: u32le + + #partial switch info.info_size { + case .ABBR_52 ..= .V5: + // All possible BMP header sizes 52+ bytes long, includes V4 + V5 + // Bit fields were read as part of the header + // V3 header is 40 bytes. We need 56 at a minimum for RGBA bit fields in the next section. + if info.info_size >= .ABBR_56 { + alpha_mask = true + } + + case .V3: + // Version 3 doesn't have a bit field embedded, but can still have a 3 or 4 color bit field. + // Because it wasn't read as part of the header, we need to read it now. + + if info.compression == .Alpha_Bit_Fields { + bf = compress.read_data(ctx, [4]u32le) or_return + alpha_mask = true + read = 16 + } else { + bf.xyz = compress.read_data(ctx, [3]u32le) or_return + read = 12 + } + + case: + // Bit fields are unhandled for this BMP version + return {}, 0, .Bitfield_Version_Unhandled + } + + if alpha_mask { + res = { + mask = {bf.r, bf.g, bf.b, bf.a}, + shift = {ctz(bf.r), ctz(bf.g), ctz(bf.b), ctz(bf.a)}, + bits = {c1s(bf.r), c1s(bf.g), c1s(bf.b), c1s(bf.a)}, + } + + bit_count = res.bits.r + res.bits.g + res.bits.b + res.bits.a + } else { + res = { + mask = {bf.r, bf.g, bf.b, 0}, + shift = {ctz(bf.r), ctz(bf.g), ctz(bf.b), 0}, + bits = {c1s(bf.r), c1s(bf.g), c1s(bf.b), 0}, + } + + bit_count = res.bits.r + res.bits.g + res.bits.b + } + + if bit_count > u32le(info.bpp) { + err = .Bitfield_Sum_Exceeds_BPP + } + + overlapped := res.mask.r | res.mask.g | res.mask.b | res.mask.a + if c1s(overlapped) < bit_count { + err = .Bitfield_Overlapped + } + return res, read, err + + case: + return {}, 0, .Unsupported_Compression + } + return +} + +scale :: proc(val: $T, mask, shift, bits: u32le) -> (res: u8) { + if bits == 0 { return 0 } // Guard against malformed bit fields + v := (u32le(val) & mask) >> shift + mask_in := u32le(1 << bits) - 1 + return u8(v * 255 / mask_in) +} + +decode_rgb :: proc(ctx: ^$C, img: ^Image, info: image.BMP_Header, allocator := context.allocator) -> (err: Error) { + pixel_offset := int(info.pixel_offset) + pixel_offset -= int(info.info_size) + FILE_HEADER_SIZE + + palette: [256]RGBA_Pixel + + // Palette size is info.colors_used if populated. If not it's min(1 << bpp, offset to the pixels / channel count) + colors_used := min(256, 1 << info.bpp if info.colors_used == 0 else info.colors_used) + max_colors := pixel_offset / 3 if info.info_size == .OS2_v1 else pixel_offset / 4 + colors_used = min(colors_used, u32le(max_colors)) + + switch info.bpp { + case 1: + if info.info_size == .OS2_v1 { + // 2 x RGB palette of instead of variable RGBA palette + for i in 0..> shift) & 0x01 + write(img, x, y, palette[p].bgr) or_return + } + } + + case 2: // Non-standard on modern Windows, but was allowed on WinCE + for i in 0..> u8(shift)) & 0x03 + write(img, x, y, palette[p].bgr) or_return + } + } + + case 4: + if info.info_size == .OS2_v1 { + // 16 x RGB palette of instead of variable RGBA palette + for i in 0..> 4 if x & 1 == 0 else data[x / 2] + write(img, x, y, palette[p & 0x0f].bgr) or_return + } + } + + case 8: + if info.info_size == .OS2_v1 { + // 256 x RGB palette of instead of variable RGBA palette + for i in 0.. (err: Error) { + pixel_offset := int(info.pixel_offset) + pixel_offset -= int(info.info_size) + FILE_HEADER_SIZE + + bytes_needed := size_of(RGB_Pixel) * img.height * img.width + if resize(&img.pixels.buf, bytes_needed) != nil { + return .Unable_To_Allocate_Or_Resize + } + out := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:]) + assert(len(out) == img.height * img.width) + + palette: [256]RGBA_Pixel + + switch info.bpp { + case 4: + colors_used := info.colors_used if info.colors_used > 0 else 16 + colors_used = min(colors_used, 16) + + for i in 0.. 0 { + for count in 0..> 0) & 0x0f].bgr) + } else { + write(img, x, y, palette[(data[index + 1] >> 4) & 0x0f].bgr) + } + x += 1 + } + index += 2 + } else { + switch data[index + 1] { + case 0: // EOL + x = 0; y += 1 + index += 2 + case 1: // EOB + return + case 2: // MOVE + x += int(data[index + 2]) + y += int(data[index + 3]) + index += 4 + case: // Literals + run_length := int(data[index + 1]) + aligned := (align4(run_length) >> 1) + 2 + + if index + aligned >= len(data) { + return .Corrupt + } + + for count in 0..> 4 + } + write(img, x, y, palette[val].bgr) + x += 1 + } + index += aligned + } + } + } + + case 8: + colors_used := info.colors_used if info.colors_used > 0 else 256 + colors_used = min(colors_used, 256) + + for i in 0.. 0 { + for _ in 0..= len(data) { + return .Corrupt + } + for count in 0.. (stride: int) { + stride = width + if width & 1 != 0 { + stride += 2 - (width & 1) + } + return +} + +align4 :: proc(width: int) -> (stride: int) { + stride = width + if width & 3 != 0 { + stride += 4 - (width & 3) + } + return +} + +skip_space :: proc(ctx: ^$C, bytes_to_skip: int) -> (err: Error) { + if bytes_to_skip < 0 { + return .Corrupt + } + for _ in 0.. (img: ^Image, err: Error) { + context.allocator = allocator + + data, ok := os.read_entire_file(filename) + defer delete(data) + + if ok { + return load_from_bytes(data, options) + } else { + return nil, .Unable_To_Read_File + } +} + +save :: proc{save_to_buffer, save_to_file} + +save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + out := &bytes.Buffer{} + defer bytes.buffer_destroy(out) + + save_to_buffer(out, img, options) or_return + write_ok := os.write_entire_file(output, out.buf[:]) + + return nil if write_ok else .Unable_To_Write_File +} \ No newline at end of file diff --git a/core/image/common.odin b/core/image/common.odin index b576a9521..2b1f71711 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -12,6 +12,7 @@ package image import "core:bytes" import "core:mem" +import "core:io" import "core:compress" import "base:runtime" @@ -62,6 +63,7 @@ Image_Metadata :: union #shared_nil { ^PNG_Info, ^QOI_Info, ^TGA_Info, + ^BMP_Info, } @@ -159,11 +161,13 @@ Error :: union #shared_nil { Netpbm_Error, PNG_Error, QOI_Error, + BMP_Error, compress.Error, compress.General_Error, compress.Deflate_Error, compress.ZLIB_Error, + io.Error, runtime.Allocator_Error, } @@ -196,6 +200,128 @@ General_Image_Error :: enum { Unable_To_Allocate_Or_Resize, } +/* + BMP-specific +*/ +BMP_Error :: enum { + None = 0, + Invalid_File_Size, + Unsupported_BMP_Version, + Unsupported_OS2_File, + Unsupported_Compression, + Unsupported_BPP, + Invalid_Stride, + Invalid_Color_Count, + Implausible_File_Size, + Bitfield_Version_Unhandled, // We don't (yet) handle bit fields for this BMP version. + Bitfield_Sum_Exceeds_BPP, // Total mask bit count > bpp + Bitfield_Overlapped, // Channel masks overlap +} + +// img.metadata is wrapped in a struct in case we need to add to it later +// without putting it in BMP_Header +BMP_Info :: struct { + info: BMP_Header, +} + +BMP_Magic :: enum u16le { + Bitmap = 0x4d42, // 'BM' + OS2_Bitmap_Array = 0x4142, // 'BA' + OS2_Icon = 0x4349, // 'IC', + OS2_Color_Icon = 0x4943, // 'CI' + OS2_Pointer = 0x5450, // 'PT' + OS2_Color_Pointer = 0x5043, // 'CP' +} + +// See: http://justsolve.archiveteam.org/wiki/BMP#Well-known_versions +BMP_Version :: enum u32le { + OS2_v1 = 12, // BITMAPCOREHEADER (Windows V2 / OS/2 version 1.0) + OS2_v2 = 64, // BITMAPCOREHEADER2 (OS/2 version 2.x) + V3 = 40, // BITMAPINFOHEADER + V4 = 108, // BITMAPV4HEADER + V5 = 124, // BITMAPV5HEADER + + ABBR_16 = 16, // Abbreviated + ABBR_24 = 24, // .. + ABBR_48 = 48, // .. + ABBR_52 = 52, // .. + ABBR_56 = 56, // .. +} + +BMP_Header :: struct #packed { + // File header + magic: BMP_Magic, + size: u32le, + _res1: u16le, // Reserved; must be zero + _res2: u16le, // Reserved; must be zero + pixel_offset: u32le, // Offset in bytes, from the beginning of BMP_Header to the pixel data + // V3 + info_size: BMP_Version, + width: i32le, + height: i32le, + planes: u16le, + bpp: u16le, + compression: BMP_Compression, + image_size: u32le, + pels_per_meter: [2]u32le, + colors_used: u32le, + colors_important: u32le, // OS2_v2 is equal up to here + // V4 + masks: [4]u32le `fmt:"32b"`, + colorspace: BMP_Logical_Color_Space, + endpoints: BMP_CIEXYZTRIPLE, + gamma: [3]BMP_GAMMA16_16, + // V5 + intent: BMP_Gamut_Mapping_Intent, + profile_data: u32le, + profile_size: u32le, + reserved: u32le, +} +#assert(size_of(BMP_Header) == 138) + +OS2_Header :: struct #packed { + // BITMAPCOREHEADER minus info_size field + width: i16le, + height: i16le, + planes: u16le, + bpp: u16le, +} +#assert(size_of(OS2_Header) == 8) + +BMP_Compression :: enum u32le { + RGB = 0x0000, + RLE8 = 0x0001, + RLE4 = 0x0002, + Bit_Fields = 0x0003, // If Windows + Huffman1D = 0x0003, // If OS2v2 + JPEG = 0x0004, // If Windows + RLE24 = 0x0004, // If OS2v2 + PNG = 0x0005, + Alpha_Bit_Fields = 0x0006, + CMYK = 0x000B, + CMYK_RLE8 = 0x000C, + CMYK_RLE4 = 0x000D, +} + +BMP_Logical_Color_Space :: enum u32le { + CALIBRATED_RGB = 0x00000000, + sRGB = 0x73524742, // 'sRGB' + WINDOWS_COLOR_SPACE = 0x57696E20, // 'Win ' +} + +BMP_FXPT2DOT30 :: u32le +BMP_CIEXYZ :: [3]BMP_FXPT2DOT30 +BMP_CIEXYZTRIPLE :: [3]BMP_CIEXYZ +BMP_GAMMA16_16 :: [2]u16le + +BMP_Gamut_Mapping_Intent :: enum u32le { + INVALID = 0x00000000, // If not V5, this field will just be zero-initialized and not valid. + ABS_COLORIMETRIC = 0x00000008, + BUSINESS = 0x00000001, + GRAPHICS = 0x00000002, + IMAGES = 0x00000004, +} + /* Netpbm-specific definitions */ @@ -1133,6 +1259,40 @@ apply_palette_rgba :: proc(img: ^Image, palette: [256]RGBA_Pixel, allocator := c } apply_palette :: proc{apply_palette_rgb, apply_palette_rgba} +blend_single_channel :: #force_inline proc(fg, alpha, bg: $T) -> (res: T) where T == u8 || T == u16 { + MAX :: 256 when T == u8 else 65536 + + c := u32(fg) * (MAX - u32(alpha)) + u32(bg) * (1 + u32(alpha)) + 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 { + MAX :: 256 when T == u8 else 65536 + + when N == 1 { + r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha)) + return {T(r & (MAX - 1))} + } + when N == 2 { + r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha)) + g := u32(fg.g) * (MAX - u32(alpha)) + u32(bg.g) * (1 + u32(alpha)) + return {T(r & (MAX - 1)), T(g & (MAX - 1))} + } + when N == 3 || N == 4 { + r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha)) + g := u32(fg.g) * (MAX - u32(alpha)) + u32(bg.g) * (1 + u32(alpha)) + b := u32(fg.b) * (MAX - u32(alpha)) + u32(bg.b) * (1 + u32(alpha)) + + when N == 3 { + return {T(r & (MAX - 1)), T(g & (MAX - 1)), T(b & (MAX - 1))} + } else { + return {T(r & (MAX - 1)), T(g & (MAX - 1)), T(b & (MAX - 1)), MAX - 1} + } + } + unreachable() +} +blend :: proc{blend_single_channel, blend_pixel} + // Replicates grayscale values into RGB(A) 8- or 16-bit images as appropriate. // Returns early with `false` if already an RGB(A) image. @@ -1153,55 +1313,55 @@ expand_grayscale :: proc(img: ^Image, allocator := context.allocator) -> (ok: bo } switch img.depth { - case 8: - switch img.channels { - case 1: // Turn Gray into RGB - out := mem.slice_data_cast([]RGB_Pixel, buf.buf[:]) + case 8: + switch img.channels { + case 1: // Turn Gray into RGB + out := mem.slice_data_cast([]RGB_Pixel, buf.buf[:]) - for p in img.pixels.buf { - out[0] = p // Broadcast gray value into RGB components. - out = out[1:] - } - - case 2: // Turn Gray + Alpha into RGBA - inp := mem.slice_data_cast([]GA_Pixel, img.pixels.buf[:]) - out := mem.slice_data_cast([]RGBA_Pixel, buf.buf[:]) - - for p in inp { - out[0].rgb = p.r // Gray component. - out[0].a = p.g // Alpha component. - } - - case: - unreachable() + for p in img.pixels.buf { + out[0] = p // Broadcast gray value into RGB components. + out = out[1:] } - case 16: - switch img.channels { - case 1: // Turn Gray into RGB - inp := mem.slice_data_cast([]u16, img.pixels.buf[:]) - out := mem.slice_data_cast([]RGB_Pixel_16, buf.buf[:]) + case 2: // Turn Gray + Alpha into RGBA + inp := mem.slice_data_cast([]GA_Pixel, img.pixels.buf[:]) + out := mem.slice_data_cast([]RGBA_Pixel, buf.buf[:]) - for p in inp { - out[0] = p // Broadcast gray value into RGB components. - out = out[1:] - } - - case 2: // Turn Gray + Alpha into RGBA - inp := mem.slice_data_cast([]GA_Pixel_16, img.pixels.buf[:]) - out := mem.slice_data_cast([]RGBA_Pixel_16, buf.buf[:]) - - for p in inp { - out[0].rgb = p.r // Gray component. - out[0].a = p.g // Alpha component. - } - - case: - unreachable() + for p in inp { + out[0].rgb = p.r // Gray component. + out[0].a = p.g // Alpha component. } case: unreachable() + } + + case 16: + switch img.channels { + case 1: // Turn Gray into RGB + inp := mem.slice_data_cast([]u16, img.pixels.buf[:]) + out := mem.slice_data_cast([]RGB_Pixel_16, buf.buf[:]) + + for p in inp { + out[0] = p // Broadcast gray value into RGB components. + out = out[1:] + } + + case 2: // Turn Gray + Alpha into RGBA + inp := mem.slice_data_cast([]GA_Pixel_16, img.pixels.buf[:]) + out := mem.slice_data_cast([]RGBA_Pixel_16, buf.buf[:]) + + for p in inp { + out[0].rgb = p.r // Gray component. + out[0].a = p.g // Alpha component. + } + + case: + unreachable() + } + + case: + unreachable() } @@ -1216,7 +1376,7 @@ expand_grayscale :: proc(img: ^Image, allocator := context.allocator) -> (ok: bo /* Helper functions to read and write data from/to a Context, etc. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_data :: proc(z: $C, $T: typeid) -> (res: T, err: compress.General_Error) { if r, e := compress.read_data(z, T); e != .None { return {}, .Stream_Too_Short @@ -1225,7 +1385,7 @@ read_data :: proc(z: $C, $T: typeid) -> (res: T, err: compress.General_Error) { } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_u8 :: proc(z: $C) -> (res: u8, err: compress.General_Error) { if r, e := compress.read_u8(z); e != .None { return {}, .Stream_Too_Short diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 4bb070da8..177269722 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -341,7 +341,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a options := options if .info in options { - options |= {.return_metadata, .do_not_decompress_image} + options += {.return_metadata, .do_not_decompress_image} options -= {.info} } @@ -354,7 +354,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } if .do_not_expand_channels in options { - options |= {.do_not_expand_grayscale, .do_not_expand_indexed} + options += {.do_not_expand_grayscale, .do_not_expand_indexed} } if img == nil { @@ -535,28 +535,28 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a ct := transmute(u8)info.header.color_type switch ct { - case 3: // Indexed color - if c.header.length != 1 { - return {}, .BKGD_Invalid_Length - } - col := _plte.entries[c.data[0]] - img.background = [3]u16{ - u16(col[0]) << 8 | u16(col[0]), - u16(col[1]) << 8 | u16(col[1]), - u16(col[2]) << 8 | u16(col[2]), - } - case 0, 4: // Grayscale, with and without Alpha - if c.header.length != 2 { - return {}, .BKGD_Invalid_Length - } - col := u16(mem.slice_data_cast([]u16be, c.data[:])[0]) - img.background = [3]u16{col, col, col} - case 2, 6: // Color, with and without Alpha - if c.header.length != 6 { - return {}, .BKGD_Invalid_Length - } - col := mem.slice_data_cast([]u16be, c.data[:]) - img.background = [3]u16{u16(col[0]), u16(col[1]), u16(col[2])} + case 3: // Indexed color + if c.header.length != 1 { + return {}, .BKGD_Invalid_Length + } + col := _plte.entries[c.data[0]] + img.background = [3]u16{ + u16(col[0]) << 8 | u16(col[0]), + u16(col[1]) << 8 | u16(col[1]), + u16(col[2]) << 8 | u16(col[2]), + } + case 0, 4: // Grayscale, with and without Alpha + if c.header.length != 2 { + return {}, .BKGD_Invalid_Length + } + col := u16(mem.slice_data_cast([]u16be, c.data[:])[0]) + img.background = [3]u16{col, col, col} + case 2, 6: // Color, with and without Alpha + if c.header.length != 6 { + return {}, .BKGD_Invalid_Length + } + col := mem.slice_data_cast([]u16be, c.data[:]) + img.background = [3]u16{u16(col[0]), u16(col[1]), u16(col[2])} } case .tRNS: @@ -597,7 +597,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a dsc := depth_scale_table scale := dsc[info.header.bit_depth] if scale != 1 { - key := mem.slice_data_cast([]u16be, c.data)[0] * u16be(scale) + key := (^u16be)(raw_data(c.data))^ * u16be(scale) c.data = []u8{0, u8(key & 255)} } } @@ -735,59 +735,48 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a return {}, .Unable_To_Allocate_Or_Resize } - i := 0; j := 0 - // If we don't have transparency or drop it without applying it, we can do this: if (!seen_trns || (seen_trns && .alpha_drop_if_present in options && .alpha_premultiply not_in options)) && .alpha_add_if_missing not_in options { - for h := 0; h < int(img.height); h += 1 { - for w := 0; w < int(img.width); w += 1 { - c := _plte.entries[temp.buf[i]] - t.buf[j ] = c.r - t.buf[j+1] = c.g - t.buf[j+2] = c.b - i += 1; j += 3 - } + output := mem.slice_data_cast([]image.RGB_Pixel, t.buf[:]) + for pal_idx, idx in temp.buf { + output[idx] = _plte.entries[pal_idx] } } else if add_alpha || .alpha_drop_if_present in options { - bg := [3]f32{0, 0, 0} + bg := PLTE_Entry{0, 0, 0} if premultiply && seen_bkgd { c16 := img.background.([3]u16) - bg = [3]f32{f32(c16.r), f32(c16.g), f32(c16.b)} + bg = {u8(c16.r), u8(c16.g), u8(c16.b)} } no_alpha := (.alpha_drop_if_present in options || premultiply) && .alpha_add_if_missing not_in options blend_background := seen_bkgd && .blend_background in options - for h := 0; h < int(img.height); h += 1 { - for w := 0; w < int(img.width); w += 1 { - index := temp.buf[i] - - c := _plte.entries[index] - a := int(index) < len(trns.data) ? trns.data[index] : 255 - alpha := f32(a) / 255.0 + if no_alpha { + output := mem.slice_data_cast([]image.RGB_Pixel, t.buf[:]) + for orig, idx in temp.buf { + c := _plte.entries[orig] + a := int(orig) < len(trns.data) ? trns.data[orig] : 255 if blend_background { - c.r = u8((1.0 - alpha) * bg[0] + f32(c.r) * alpha) - c.g = u8((1.0 - alpha) * bg[1] + f32(c.g) * alpha) - c.b = u8((1.0 - alpha) * bg[2] + f32(c.b) * alpha) + output[idx] = image.blend(c, a, bg) + } else if premultiply { + output[idx] = image.blend(PLTE_Entry{}, a, c) + } + } + } else { + output := mem.slice_data_cast([]image.RGBA_Pixel, t.buf[:]) + for orig, idx in temp.buf { + c := _plte.entries[orig] + a := int(orig) < len(trns.data) ? trns.data[orig] : 255 + + if blend_background { + c = image.blend(c, a, bg) a = 255 } else if premultiply { - c.r = u8(f32(c.r) * alpha) - c.g = u8(f32(c.g) * alpha) - c.b = u8(f32(c.b) * alpha) + c = image.blend(PLTE_Entry{}, a, c) } - t.buf[j ] = c.r - t.buf[j+1] = c.g - t.buf[j+2] = c.b - i += 1 - - if no_alpha { - j += 3 - } else { - t.buf[j+3] = u8(a) - j += 4 - } + output[idx] = {c.r, c.g, c.b, u8(a)} } } } else { @@ -1015,8 +1004,8 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a return {}, .Unable_To_Allocate_Or_Resize } - p := mem.slice_data_cast([]u8, temp.buf[:]) - o := mem.slice_data_cast([]u8, t.buf[:]) + p := temp.buf[:] + o := t.buf[:] switch raw_image_channels { case 1: @@ -1627,7 +1616,6 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH return nil } - @(init, private) _register :: proc() { image.register(.PNG, load_from_bytes, destroy) diff --git a/core/image/qoi/qoi.odin b/core/image/qoi/qoi.odin index dfdf1875a..5cf252fcc 100644 --- a/core/image/qoi/qoi.odin +++ b/core/image/qoi/qoi.odin @@ -1,381 +1,376 @@ -/* - Copyright 2022 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - List of contributors: - Jeroen van Rijn: Initial implementation. -*/ - - -// package qoi implements a QOI image reader -// -// The QOI specification is at https://qoiformat.org. -package qoi - -import "core:image" -import "core:compress" -import "core:bytes" - -Error :: image.Error -Image :: image.Image -Options :: image.Options - -RGB_Pixel :: image.RGB_Pixel -RGBA_Pixel :: image.RGBA_Pixel - -save_to_buffer :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - if img == nil { - return .Invalid_Input_Image - } - - if output == nil { - return .Invalid_Output - } - - pixels := img.width * img.height - if pixels == 0 || pixels > image.MAX_DIMENSIONS { - return .Invalid_Input_Image - } - - // QOI supports only 8-bit images with 3 or 4 channels. - if img.depth != 8 || img.channels < 3 || img.channels > 4 { - return .Invalid_Input_Image - } - - if img.channels * pixels != len(img.pixels.buf) { - return .Invalid_Input_Image - } - - written := 0 - - // Calculate and allocate maximum size. We'll reclaim space to actually written output at the end. - max_size := pixels * (img.channels + 1) + size_of(image.QOI_Header) + size_of(u64be) - - if resize(&output.buf, max_size) != nil { - return .Unable_To_Allocate_Or_Resize - } - - header := image.QOI_Header{ - magic = image.QOI_Magic, - width = u32be(img.width), - height = u32be(img.height), - channels = u8(img.channels), - color_space = .Linear if .qoi_all_channels_linear in options else .sRGB, - } - header_bytes := transmute([size_of(image.QOI_Header)]u8)header - - copy(output.buf[written:], header_bytes[:]) - written += size_of(image.QOI_Header) - - /* - Encode loop starts here. - */ - seen: [64]RGBA_Pixel - pix := RGBA_Pixel{0, 0, 0, 255} - prev := pix - - seen[qoi_hash(pix)] = pix - - input := img.pixels.buf[:] - run := u8(0) - - for len(input) > 0 { - if img.channels == 4 { - pix = (^RGBA_Pixel)(raw_data(input))^ - } else { - pix.rgb = (^RGB_Pixel)(raw_data(input))^ - } - input = input[img.channels:] - - if pix == prev { - run += 1 - // As long as the pixel matches the last one, accumulate the run total. - // If we reach the max run length or the end of the image, write the run. - if run == 62 || len(input) == 0 { - // Encode and write run - output.buf[written] = u8(QOI_Opcode_Tag.RUN) | (run - 1) - written += 1 - run = 0 - } - } else { - if run > 0 { - // The pixel differs from the previous one, but we still need to write the pending run. - // Encode and write run - output.buf[written] = u8(QOI_Opcode_Tag.RUN) | (run - 1) - written += 1 - run = 0 - } - - index := qoi_hash(pix) - - if seen[index] == pix { - // Write indexed pixel - output.buf[written] = u8(QOI_Opcode_Tag.INDEX) | index - written += 1 - } else { - // Add pixel to index - seen[index] = pix - - // If the alpha matches the previous pixel's alpha, we don't need to write a full RGBA literal. - if pix.a == prev.a { - // Delta - d := pix.rgb - prev.rgb - - // DIFF, biased and modulo 256 - _d := d + 2 - - // LUMA, biased and modulo 256 - _l := RGB_Pixel{ d.r - d.g + 8, d.g + 32, d.b - d.g + 8 } - - if _d.r < 4 && _d.g < 4 && _d.b < 4 { - // Delta is between -2 and 1 inclusive - output.buf[written] = u8(QOI_Opcode_Tag.DIFF) | _d.r << 4 | _d.g << 2 | _d.b - written += 1 - } else if _l.r < 16 && _l.g < 64 && _l.b < 16 { - // Biased luma is between {-8..7, -32..31, -8..7} - output.buf[written ] = u8(QOI_Opcode_Tag.LUMA) | _l.g - output.buf[written + 1] = _l.r << 4 | _l.b - written += 2 - } else { - // Write RGB literal - output.buf[written] = u8(QOI_Opcode_Tag.RGB) - pix_bytes := transmute([4]u8)pix - copy(output.buf[written + 1:], pix_bytes[:3]) - written += 4 - } - } else { - // Write RGBA literal - output.buf[written] = u8(QOI_Opcode_Tag.RGBA) - pix_bytes := transmute([4]u8)pix - copy(output.buf[written + 1:], pix_bytes[:]) - written += 5 - } - } - } - prev = pix - } - - trailer := []u8{0, 0, 0, 0, 0, 0, 0, 1} - copy(output.buf[written:], trailer[:]) - written += len(trailer) - - resize(&output.buf, written) - return nil -} - -load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { - ctx := &compress.Context_Memory_Input{ - input_data = data, - } - - img, err = load_from_context(ctx, options, allocator) - return img, err -} - -@(optimization_mode="speed") -load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { - context.allocator = allocator - options := options - - if .info in options { - options |= {.return_metadata, .do_not_decompress_image} - options -= {.info} - } - - if .return_header in options && .return_metadata in options { - options -= {.return_header} - } - - header := image.read_data(ctx, image.QOI_Header) or_return - if header.magic != image.QOI_Magic { - return img, .Invalid_Signature - } - - if img == nil { - img = new(Image) - } - img.which = .QOI - - if .return_metadata in options { - info := new(image.QOI_Info) - info.header = header - img.metadata = info - } - - if header.channels != 3 && header.channels != 4 { - return img, .Invalid_Number_Of_Channels - } - - if header.color_space != .sRGB && header.color_space != .Linear { - return img, .Invalid_Color_Space - } - - if header.width == 0 || header.height == 0 { - return img, .Invalid_Image_Dimensions - } - - total_pixels := header.width * header.height - if total_pixels > image.MAX_DIMENSIONS { - return img, .Image_Dimensions_Too_Large - } - - img.width = int(header.width) - img.height = int(header.height) - img.channels = 4 if .alpha_add_if_missing in options else int(header.channels) - img.depth = 8 - - if .do_not_decompress_image in options { - img.channels = int(header.channels) - return - } - - bytes_needed := image.compute_buffer_size(int(header.width), int(header.height), img.channels, 8) - - if resize(&img.pixels.buf, bytes_needed) != nil { - return img, .Unable_To_Allocate_Or_Resize - } - - /* - Decode loop starts here. - */ - seen: [64]RGBA_Pixel - pix := RGBA_Pixel{0, 0, 0, 255} - seen[qoi_hash(pix)] = pix - pixels := img.pixels.buf[:] - - decode: for len(pixels) > 0 { - data := image.read_u8(ctx) or_return - - tag := QOI_Opcode_Tag(data) - #partial switch tag { - case .RGB: - pix.rgb = image.read_data(ctx, RGB_Pixel) or_return - - #no_bounds_check { - seen[qoi_hash(pix)] = pix - } - - case .RGBA: - pix = image.read_data(ctx, RGBA_Pixel) or_return - - #no_bounds_check { - seen[qoi_hash(pix)] = pix - } - - case: - // 2-bit tag - tag = QOI_Opcode_Tag(data & QOI_Opcode_Mask) - #partial switch tag { - case .INDEX: - pix = seen[data & 63] - - case .DIFF: - diff_r := ((data >> 4) & 3) - 2 - diff_g := ((data >> 2) & 3) - 2 - diff_b := ((data >> 0) & 3) - 2 - - pix += {diff_r, diff_g, diff_b, 0} - - #no_bounds_check { - seen[qoi_hash(pix)] = pix - } - - case .LUMA: - data2 := image.read_u8(ctx) or_return - - diff_g := (data & 63) - 32 - diff_r := diff_g - 8 + ((data2 >> 4) & 15) - diff_b := diff_g - 8 + (data2 & 15) - - pix += {diff_r, diff_g, diff_b, 0} - - #no_bounds_check { - seen[qoi_hash(pix)] = pix - } - - case .RUN: - if length := int(data & 63) + 1; (length * img.channels) > len(pixels) { - return img, .Corrupt - } else { - #no_bounds_check for _ in 0.. (index: u8) { - i1 := u16(pixel.r) * 3 - i2 := u16(pixel.g) * 5 - i3 := u16(pixel.b) * 7 - i4 := u16(pixel.a) * 11 - - return u8((i1 + i2 + i3 + i4) & 63) -} - -@(init, private) -_register :: proc() { - image.register(.QOI, load_from_bytes, destroy) -} \ No newline at end of file +/* + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Jeroen van Rijn: Initial implementation. +*/ + + +// package qoi implements a QOI image reader +// +// The QOI specification is at https://qoiformat.org. +package qoi + +import "core:image" +import "core:compress" +import "core:bytes" + +Error :: image.Error +Image :: image.Image +Options :: image.Options + +RGB_Pixel :: image.RGB_Pixel +RGBA_Pixel :: image.RGBA_Pixel + +save_to_buffer :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + if img == nil { + return .Invalid_Input_Image + } + + if output == nil { + return .Invalid_Output + } + + pixels := img.width * img.height + if pixels == 0 || pixels > image.MAX_DIMENSIONS { + return .Invalid_Input_Image + } + + // QOI supports only 8-bit images with 3 or 4 channels. + if img.depth != 8 || img.channels < 3 || img.channels > 4 { + return .Invalid_Input_Image + } + + if img.channels * pixels != len(img.pixels.buf) { + return .Invalid_Input_Image + } + + written := 0 + + // Calculate and allocate maximum size. We'll reclaim space to actually written output at the end. + max_size := pixels * (img.channels + 1) + size_of(image.QOI_Header) + size_of(u64be) + + if resize(&output.buf, max_size) != nil { + return .Unable_To_Allocate_Or_Resize + } + + header := image.QOI_Header{ + magic = image.QOI_Magic, + width = u32be(img.width), + height = u32be(img.height), + channels = u8(img.channels), + color_space = .Linear if .qoi_all_channels_linear in options else .sRGB, + } + header_bytes := transmute([size_of(image.QOI_Header)]u8)header + + copy(output.buf[written:], header_bytes[:]) + written += size_of(image.QOI_Header) + + /* + Encode loop starts here. + */ + seen: [64]RGBA_Pixel + pix := RGBA_Pixel{0, 0, 0, 255} + prev := pix + + input := img.pixels.buf[:] + run := u8(0) + + for len(input) > 0 { + if img.channels == 4 { + pix = (^RGBA_Pixel)(raw_data(input))^ + } else { + pix.rgb = (^RGB_Pixel)(raw_data(input))^ + } + input = input[img.channels:] + + if pix == prev { + run += 1 + // As long as the pixel matches the last one, accumulate the run total. + // If we reach the max run length or the end of the image, write the run. + if run == 62 || len(input) == 0 { + // Encode and write run + output.buf[written] = u8(QOI_Opcode_Tag.RUN) | (run - 1) + written += 1 + run = 0 + } + } else { + if run > 0 { + // The pixel differs from the previous one, but we still need to write the pending run. + // Encode and write run + output.buf[written] = u8(QOI_Opcode_Tag.RUN) | (run - 1) + written += 1 + run = 0 + } + + index := qoi_hash(pix) + + if seen[index] == pix { + // Write indexed pixel + output.buf[written] = u8(QOI_Opcode_Tag.INDEX) | index + written += 1 + } else { + // Add pixel to index + seen[index] = pix + + // If the alpha matches the previous pixel's alpha, we don't need to write a full RGBA literal. + if pix.a == prev.a { + // Delta + d := pix.rgb - prev.rgb + + // DIFF, biased and modulo 256 + _d := d + 2 + + // LUMA, biased and modulo 256 + _l := RGB_Pixel{ d.r - d.g + 8, d.g + 32, d.b - d.g + 8 } + + if _d.r < 4 && _d.g < 4 && _d.b < 4 { + // Delta is between -2 and 1 inclusive + output.buf[written] = u8(QOI_Opcode_Tag.DIFF) | _d.r << 4 | _d.g << 2 | _d.b + written += 1 + } else if _l.r < 16 && _l.g < 64 && _l.b < 16 { + // Biased luma is between {-8..7, -32..31, -8..7} + output.buf[written ] = u8(QOI_Opcode_Tag.LUMA) | _l.g + output.buf[written + 1] = _l.r << 4 | _l.b + written += 2 + } else { + // Write RGB literal + output.buf[written] = u8(QOI_Opcode_Tag.RGB) + copy(output.buf[written + 1:], pix[:3]) + written += 4 + } + } else { + // Write RGBA literal + output.buf[written] = u8(QOI_Opcode_Tag.RGBA) + copy(output.buf[written + 1:], pix[:]) + written += 5 + } + } + } + prev = pix + } + + trailer := []u8{0, 0, 0, 0, 0, 0, 0, 1} + copy(output.buf[written:], trailer[:]) + written += len(trailer) + + resize(&output.buf, written) + return nil +} + +load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + ctx := &compress.Context_Memory_Input{ + input_data = data, + } + + img, err = load_from_context(ctx, options, allocator) + return img, err +} + +@(optimization_mode="favor_size") +load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + context.allocator = allocator + options := options + + if .info in options { + options += {.return_metadata, .do_not_decompress_image} + options -= {.info} + } + + if .return_header in options && .return_metadata in options { + options -= {.return_header} + } + + header := image.read_data(ctx, image.QOI_Header) or_return + if header.magic != image.QOI_Magic { + return img, .Invalid_Signature + } + + if img == nil { + img = new(Image) + } + img.which = .QOI + + if .return_metadata in options { + info := new(image.QOI_Info) + info.header = header + img.metadata = info + } + + if header.channels != 3 && header.channels != 4 { + return img, .Invalid_Number_Of_Channels + } + + if header.color_space != .sRGB && header.color_space != .Linear { + return img, .Invalid_Color_Space + } + + if header.width == 0 || header.height == 0 { + return img, .Invalid_Image_Dimensions + } + + total_pixels := header.width * header.height + if total_pixels > image.MAX_DIMENSIONS { + return img, .Image_Dimensions_Too_Large + } + + img.width = int(header.width) + img.height = int(header.height) + img.channels = 4 if .alpha_add_if_missing in options else int(header.channels) + img.depth = 8 + + if .do_not_decompress_image in options { + img.channels = int(header.channels) + return + } + + bytes_needed := image.compute_buffer_size(int(header.width), int(header.height), img.channels, 8) + + if resize(&img.pixels.buf, bytes_needed) != nil { + return img, .Unable_To_Allocate_Or_Resize + } + + /* + Decode loop starts here. + */ + seen: [64]RGBA_Pixel + pix := RGBA_Pixel{0, 0, 0, 255} + pixels := img.pixels.buf[:] + + decode: for len(pixels) > 0 { + data := image.read_u8(ctx) or_return + + tag := QOI_Opcode_Tag(data) + #partial switch tag { + case .RGB: + pix.rgb = image.read_data(ctx, RGB_Pixel) or_return + + #no_bounds_check { + seen[qoi_hash(pix)] = pix + } + + case .RGBA: + pix = image.read_data(ctx, RGBA_Pixel) or_return + + #no_bounds_check { + seen[qoi_hash(pix)] = pix + } + + case: + // 2-bit tag + tag = QOI_Opcode_Tag(data & QOI_Opcode_Mask) + #partial switch tag { + case .INDEX: + pix = seen[data & 63] + + case .DIFF: + diff_r := ((data >> 4) & 3) - 2 + diff_g := ((data >> 2) & 3) - 2 + diff_b := ((data >> 0) & 3) - 2 + + pix += {diff_r, diff_g, diff_b, 0} + + #no_bounds_check { + seen[qoi_hash(pix)] = pix + } + + case .LUMA: + data2 := image.read_u8(ctx) or_return + + diff_g := (data & 63) - 32 + diff_r := diff_g - 8 + ((data2 >> 4) & 15) + diff_b := diff_g - 8 + (data2 & 15) + + pix += {diff_r, diff_g, diff_b, 0} + + #no_bounds_check { + seen[qoi_hash(pix)] = pix + } + + case .RUN: + if length := int(data & 63) + 1; (length * img.channels) > len(pixels) { + return img, .Corrupt + } else { + #no_bounds_check for _ in 0.. (index: u8) { + i1 := u16(pixel.r) * 3 + i2 := u16(pixel.g) * 5 + i3 := u16(pixel.b) * 7 + i4 := u16(pixel.a) * 11 + + return u8((i1 + i2 + i3 + i4) & 63) +} + +@(init, private) +_register :: proc() { + image.register(.QOI, load_from_bytes, destroy) +} diff --git a/core/image/tga/tga.odin b/core/image/tga/tga.odin index 03ef1a386..46e37a0cf 100644 --- a/core/image/tga/tga.odin +++ b/core/image/tga/tga.odin @@ -100,7 +100,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } if .info in options { - options |= {.return_metadata, .do_not_decompress_image} + options += {.return_metadata, .do_not_decompress_image} options -= {.info} } diff --git a/core/io/io.odin b/core/io/io.odin index 961dbe43e..6072aec6d 100644 --- a/core/io/io.odin +++ b/core/io/io.odin @@ -375,10 +375,6 @@ write_at_least :: proc(w: Writer, buf: []byte, min: int) -> (n: int, err: Error) nn, err = write(w, buf[n:]) n += nn } - - if err == nil && n < min { - err = .Short_Write - } return } diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index d90a33524..fb968ccb6 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -1,6 +1,7 @@ //+build !freestanding package log +import "core:encoding/ansi" import "core:fmt" import "core:strings" import "core:os" @@ -42,7 +43,7 @@ create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_F return Logger{file_console_logger_proc, data, lowest, opt} } -destroy_file_logger :: proc(log: ^Logger) { +destroy_file_logger :: proc(log: Logger) { data := cast(^File_Console_Logger_Data)log.data if data.file_handle != os.INVALID_HANDLE { os.close(data.file_handle) @@ -70,18 +71,10 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. buf := strings.builder_from_bytes(backing[:]) - do_level_header(options, level, &buf) + do_level_header(options, &buf, level) when time.IS_SUPPORTED { - if Full_Timestamp_Opts & options != nil { - fmt.sbprint(&buf, "[") - t := time.now() - y, m, d := time.date(t) - h, min, s := time.clock(t) - if .Date in options { fmt.sbprintf(&buf, "%d-%02d-%02d ", y, m, d) } - if .Time in options { fmt.sbprintf(&buf, "%02d:%02d:%02d", h, min, s) } - fmt.sbprint(&buf, "] ") - } + do_time_header(options, &buf, time.now()) } do_location_header(options, &buf, location) @@ -99,12 +92,12 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text) } -do_level_header :: proc(opts: Options, level: Level, str: ^strings.Builder) { +do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) { - RESET :: "\x1b[0m" - RED :: "\x1b[31m" - YELLOW :: "\x1b[33m" - DARK_GREY :: "\x1b[90m" + RESET :: ansi.CSI + ansi.RESET + ansi.SGR + RED :: ansi.CSI + ansi.FG_RED + ansi.SGR + YELLOW :: ansi.CSI + ansi.FG_YELLOW + ansi.SGR + DARK_GREY :: ansi.CSI + ansi.FG_BRIGHT_BLACK + ansi.SGR col := RESET switch level { @@ -125,6 +118,24 @@ do_level_header :: proc(opts: Options, level: Level, str: ^strings.Builder) { } } +do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) { + when time.IS_SUPPORTED { + if Full_Timestamp_Opts & opts != nil { + fmt.sbprint(buf, "[") + y, m, d := time.date(t) + h, min, s := time.clock(t) + if .Date in opts { + fmt.sbprintf(buf, "%d-%02d-%02d", y, m, d) + if .Time in opts { + fmt.sbprint(buf, " ") + } + } + if .Time in opts { fmt.sbprintf(buf, "%02d:%02d:%02d", h, min, s) } + fmt.sbprint(buf, "] ") + } + } +} + do_location_header :: proc(opts: Options, buf: ^strings.Builder, location := #caller_location) { if Location_Header_Opts & opts == nil { return diff --git a/core/log/multi_logger.odin b/core/log/multi_logger.odin index 55c0f1436..96d0f3dbd 100644 --- a/core/log/multi_logger.odin +++ b/core/log/multi_logger.odin @@ -12,11 +12,10 @@ create_multi_logger :: proc(logs: ..Logger) -> Logger { return Logger{multi_logger_proc, data, Level.Debug, nil} } -destroy_multi_logger :: proc(log : ^Logger) { +destroy_multi_logger :: proc(log: Logger) { data := (^Multi_Logger_Data)(log.data) delete(data.loggers) - free(log.data) - log^ = nil_logger() + free(data) } multi_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, diff --git a/core/math/big/combinatorics.odin b/core/math/big/combinatorics.odin new file mode 100644 index 000000000..87c76d830 --- /dev/null +++ b/core/math/big/combinatorics.odin @@ -0,0 +1,60 @@ +package math_big + +/* + With `n` items, calculate how many ways that `r` of them can be ordered. +*/ +permutations_with_repetition :: int_pow_int + +/* + With `n` items, calculate how many ways that `r` of them can be ordered without any repeats. +*/ +permutations_without_repetition :: proc(dest: ^Int, n, r: int) -> (error: Error) { + if n == r { + return factorial(dest, n) + } + + tmp := &Int{} + defer internal_destroy(tmp) + + // n! + // -------- + // (n - r)! + factorial(dest, n) or_return + factorial(tmp, n - r) or_return + div(dest, dest, tmp) or_return + + return +} + +/* + With `n` items, calculate how many ways that `r` of them can be chosen. + + Also known as the multiset coefficient or (n multichoose k). +*/ +combinations_with_repetition :: proc(dest: ^Int, n, r: int) -> (error: Error) { + // (n + r - 1)! + // ------------ + // r! (n - 1)! + return combinations_without_repetition(dest, n + r - 1, r) +} + +/* + With `n` items, calculate how many ways that `r` of them can be chosen without any repeats. + + Also known as the binomial coefficient or (n choose k). +*/ +combinations_without_repetition :: proc(dest: ^Int, n, r: int) -> (error: Error) { + tmp_a, tmp_b := &Int{}, &Int{} + defer internal_destroy(tmp_a, tmp_b) + + // n! + // ------------ + // r! (n - r)! + factorial(dest, n) or_return + factorial(tmp_a, r) or_return + factorial(tmp_b, n - r) or_return + mul(tmp_a, tmp_a, tmp_b) or_return + div(dest, dest, tmp_a) or_return + + return +} diff --git a/core/math/big/helpers.odin b/core/math/big/helpers.odin index 35be4f1fd..ee09bb2c7 100644 --- a/core/math/big/helpers.odin +++ b/core/math/big/helpers.odin @@ -356,17 +356,17 @@ int_count_lsb :: proc(a: ^Int, allocator := context.allocator) -> (count: int, e } platform_count_lsb :: #force_inline proc(a: $T) -> (count: int) - where intrinsics.type_is_integer(T) && intrinsics.type_is_unsigned(T) { + where intrinsics.type_is_integer(T), intrinsics.type_is_unsigned(T) { return int(intrinsics.count_trailing_zeros(a)) if a > 0 else 0 } count_lsb :: proc { int_count_lsb, platform_count_lsb, } -int_random_digit :: proc(r: ^rnd.Rand = nil) -> (res: DIGIT) { +int_random_digit :: proc() -> (res: DIGIT) { when _DIGIT_BITS == 60 { // DIGIT = u64 - return DIGIT(rnd.uint64(r)) & _MASK + return DIGIT(rnd.uint64()) & _MASK } else when _DIGIT_BITS == 28 { // DIGIT = u32 - return DIGIT(rnd.uint32(r)) & _MASK + return DIGIT(rnd.uint32()) & _MASK } else { panic("Unsupported DIGIT size.") } @@ -374,12 +374,12 @@ int_random_digit :: proc(r: ^rnd.Rand = nil) -> (res: DIGIT) { return 0 // We shouldn't get here. } -int_random :: proc(dest: ^Int, bits: int, r: ^rnd.Rand = nil, allocator := context.allocator) -> (err: Error) { +int_random :: proc(dest: ^Int, bits: int, allocator := context.allocator) -> (err: Error) { /* Check that `a` is usable. */ assert_if_nil(dest) - return #force_inline internal_int_random(dest, bits, r, allocator) + return #force_inline internal_int_random(dest, bits, allocator) } random :: proc { int_random, } diff --git a/core/math/big/internal.odin b/core/math/big/internal.odin index 07e1e6c03..c9b331e55 100644 --- a/core/math/big/internal.odin +++ b/core/math/big/internal.odin @@ -546,7 +546,7 @@ internal_int_shl1 :: proc(dest, src: ^Int, allocator := context.allocator) -> (e Like `internal_int_mul_digit` but with an integer as the small input. */ internal_int_mul_integer :: proc(dest, a: ^Int, b: $T, allocator := context.allocator) -> (err: Error) -where intrinsics.type_is_integer(T) && T != DIGIT { +where intrinsics.type_is_integer(T), T != DIGIT { context.allocator = allocator t := &Int{} @@ -2178,15 +2178,20 @@ internal_int_grow :: proc(a: ^Int, digits: int, allow_shrink := false, allocator } /* - If not yet iniialized, initialize the `digit` backing with the allocator we were passed. + If not yet initialized, initialize the `digit` backing with the allocator we were passed. */ if cap == 0 { a.digit = make([dynamic]DIGIT, needed, allocator) - } else if cap != needed { + } else if cap < needed { /* `[dynamic]DIGIT` already knows what allocator was used for it, so resize will do the right thing. */ resize(&a.digit, needed) + } else if cap > needed { + /* + Same applies to builtin.shrink here as resize above + */ + builtin.shrink(&a.digit, needed) } /* Let's see if the allocation/resize worked as expected. @@ -2806,17 +2811,17 @@ internal_int_count_lsb :: proc(a: ^Int) -> (count: int, err: Error) { } internal_platform_count_lsb :: #force_inline proc(a: $T) -> (count: int) - where intrinsics.type_is_integer(T) && intrinsics.type_is_unsigned(T) { + where intrinsics.type_is_integer(T), intrinsics.type_is_unsigned(T) { return int(intrinsics.count_trailing_zeros(a)) if a > 0 else 0 } internal_count_lsb :: proc { internal_int_count_lsb, internal_platform_count_lsb, } -internal_int_random_digit :: proc(r: ^rnd.Rand = nil) -> (res: DIGIT) { +internal_int_random_digit :: proc() -> (res: DIGIT) { when _DIGIT_BITS == 60 { // DIGIT = u64 - return DIGIT(rnd.uint64(r)) & _MASK + return DIGIT(rnd.uint64()) & _MASK } else when _DIGIT_BITS == 28 { // DIGIT = u32 - return DIGIT(rnd.uint32(r)) & _MASK + return DIGIT(rnd.uint32()) & _MASK } else { panic("Unsupported DIGIT size.") } @@ -2824,7 +2829,7 @@ internal_int_random_digit :: proc(r: ^rnd.Rand = nil) -> (res: DIGIT) { return 0 // We shouldn't get here. } -internal_int_random :: proc(dest: ^Int, bits: int, r: ^rnd.Rand = nil, allocator := context.allocator) -> (err: Error) { +internal_int_random :: proc(dest: ^Int, bits: int, allocator := context.allocator) -> (err: Error) { context.allocator = allocator bits := bits @@ -2841,7 +2846,7 @@ internal_int_random :: proc(dest: ^Int, bits: int, r: ^rnd.Rand = nil, allocator #force_inline internal_grow(dest, digits) or_return for i := 0; i < digits; i += 1 { - dest.digit[i] = int_random_digit(r) & _MASK + dest.digit[i] = int_random_digit() & _MASK } if bits > 0 { dest.digit[digits - 1] &= ((1 << uint(bits)) - 1) diff --git a/core/math/big/prime.odin b/core/math/big/prime.odin index 5e7c02f37..832c75119 100644 --- a/core/math/big/prime.odin +++ b/core/math/big/prime.odin @@ -12,8 +12,6 @@ package math_big -import rnd "core:math/rand" - /* Determines if an Integer is divisible by one of the _PRIME_TABLE primes. Returns true if it is, false if not. @@ -315,7 +313,7 @@ internal_int_prime_miller_rabin :: proc(a, b: ^Int, allocator := context.allocat Assumes `a` not to be `nil` and to have been initialized. */ -internal_int_is_prime :: proc(a: ^Int, miller_rabin_trials := int(-1), miller_rabin_only := USE_MILLER_RABIN_ONLY, r: ^rnd.Rand = nil, allocator := context.allocator) -> (is_prime: bool, err: Error) { +internal_int_is_prime :: proc(a: ^Int, miller_rabin_trials := int(-1), miller_rabin_only := USE_MILLER_RABIN_ONLY, allocator := context.allocator) -> (is_prime: bool, err: Error) { context.allocator = allocator miller_rabin_trials := miller_rabin_trials @@ -461,7 +459,7 @@ internal_int_is_prime :: proc(a: ^Int, miller_rabin_trials := int(-1), miller_ra for ix := 0; ix < miller_rabin_trials; ix += 1 { // rand() guarantees the first digit to be non-zero - internal_random(b, _DIGIT_TYPE_BITS, r) or_return + internal_random(b, _DIGIT_TYPE_BITS) or_return // Reduce digit before casting because DIGIT might be bigger than // an unsigned int and "mask" on the other side is most probably not. @@ -1183,14 +1181,11 @@ internal_int_prime_next_prime :: proc(a: ^Int, trials: int, bbs_style: bool, all This is possibly the mother of all prime generation functions, muahahahahaha! */ -internal_random_prime :: proc(a: ^Int, size_in_bits: int, trials: int, flags := Primality_Flags{}, r: ^rnd.Rand = nil, allocator := context.allocator) -> (err: Error) { +internal_random_prime :: proc(a: ^Int, size_in_bits: int, trials: int, flags := Primality_Flags{}, allocator := context.allocator) -> (err: Error) { context.allocator = allocator flags := flags trials := trials - t := &Int{} - defer internal_destroy(t) - /* Sanity check the input. */ diff --git a/core/math/big/private.odin b/core/math/big/private.odin index 2ee6cfafa..bb6b9497c 100644 --- a/core/math/big/private.odin +++ b/core/math/big/private.odin @@ -787,8 +787,8 @@ _private_int_sqr_comba :: proc(dest, src: ^Int, allocator := context.allocator) /* Karatsuba squaring, computes `dest` = `src` * `src` using three half-size squarings. - See comments of `_private_int_mul_karatsuba` for details. - It is essentially the same algorithm but merely tuned to perform recursive squarings. + See comments of `_private_int_mul_karatsuba` for details. + It is essentially the same algorithm but merely tuned to perform recursive squarings. */ _private_int_sqr_karatsuba :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) { context.allocator = allocator @@ -967,7 +967,7 @@ _private_int_div_3 :: proc(quotient, numerator: ^Int, allocator := context.alloc /* b = 2^_DIGIT_BITS / 3 */ - b := _WORD(1) << _WORD(_DIGIT_BITS) / _WORD(3) + b := _WORD(1) << _WORD(_DIGIT_BITS) / _WORD(3) q := &Int{} internal_grow(q, numerator.used) or_return @@ -975,7 +975,7 @@ _private_int_div_3 :: proc(quotient, numerator: ^Int, allocator := context.alloc q.sign = numerator.sign w, t: _WORD - #no_bounds_check for ix := numerator.used; ix >= 0; ix -= 1 { + #no_bounds_check for ix := numerator.used - 1; ix >= 0; ix -= 1 { w = (w << _WORD(_DIGIT_BITS)) | _WORD(numerator.digit[ix]) if w >= 3 { /* @@ -1007,8 +1007,8 @@ _private_int_div_3 :: proc(quotient, numerator: ^Int, allocator := context.alloc */ if quotient != nil { err = clamp(q) - internal_swap(q, quotient) - } + internal_swap(q, quotient) + } internal_destroy(q) return remainder, nil } @@ -1555,24 +1555,24 @@ _private_int_gcd_lcm :: proc(res_gcd, res_lcm, a, b: ^Int, allocator := context. /* If neither `a` or `b` was zero, we need to compute `gcd`. - Get copies of `a` and `b` we can modify. - */ + Get copies of `a` and `b` we can modify. + */ u, v := &Int{}, &Int{} defer internal_destroy(u, v) internal_copy(u, a) or_return internal_copy(v, b) or_return - /* - Must be positive for the remainder of the algorithm. - */ + /* + Must be positive for the remainder of the algorithm. + */ u.sign = .Zero_or_Positive; v.sign = .Zero_or_Positive - /* - B1. Find the common power of two for `u` and `v`. - */ - u_lsb, _ := internal_count_lsb(u) - v_lsb, _ := internal_count_lsb(v) - k := min(u_lsb, v_lsb) + /* + B1. Find the common power of two for `u` and `v`. + */ + u_lsb, _ := internal_count_lsb(u) + v_lsb, _ := internal_count_lsb(v) + k := min(u_lsb, v_lsb) if k > 0 { /* @@ -1615,11 +1615,11 @@ _private_int_gcd_lcm :: proc(res_gcd, res_lcm, a, b: ^Int, allocator := context. internal_shr(v, v, b) or_return } - /* - Multiply by 2**k which we divided out at the beginning. - */ - internal_shl(temp_gcd_res, u, k) or_return - temp_gcd_res.sign = .Zero_or_Positive + /* + Multiply by 2**k which we divided out at the beginning. + */ + internal_shl(temp_gcd_res, u, k) or_return + temp_gcd_res.sign = .Zero_or_Positive /* We've computed `gcd`, either the long way, or because one of the inputs was zero. @@ -1786,8 +1786,8 @@ _private_montgomery_reduce_comba :: proc(x, n: ^Int, rho: DIGIT, allocator := co `a = a + mu * m * b**i` This is computed in place and on the fly. The multiplication - by b**i is handled by offseting which columns the results - are added to. + by b**i is handled by offseting which columns the results + are added to. Note the comba method normally doesn't handle carries in the inner loop In this case we fix the carry from the previous diff --git a/core/math/big/radix.odin b/core/math/big/radix.odin index 8d8ea734e..a5100e478 100644 --- a/core/math/big/radix.odin +++ b/core/math/big/radix.odin @@ -315,6 +315,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context atoi :: proc { int_atoi, } +string_to_int :: int_atoi /* We size for `string` by default. @@ -469,7 +470,7 @@ internal_int_pack_count :: proc(a: ^Int, $T: typeid, nails := 0) -> (size_needed Assumes `a` not to be `nil` and to have been initialized. */ internal_int_pack :: proc(a: ^Int, buf: []$T, nails := 0, order := Order.LSB_First) -> (written: int, err: Error) - where intrinsics.type_is_integer(T) && intrinsics.type_is_unsigned(T) && size_of(T) <= 16 { + where intrinsics.type_is_integer(T), intrinsics.type_is_unsigned(T), size_of(T) <= 16 { assert(nails >= 0 && nails < (size_of(T) * 8)) @@ -505,7 +506,7 @@ internal_int_pack :: proc(a: ^Int, buf: []$T, nails := 0, order := Order.LSB_Fir internal_int_unpack :: proc(a: ^Int, buf: []$T, nails := 0, order := Order.LSB_First, allocator := context.allocator) -> (err: Error) - where intrinsics.type_is_integer(T) && intrinsics.type_is_unsigned(T) && size_of(T) <= 16 { + where intrinsics.type_is_integer(T), intrinsics.type_is_unsigned(T), size_of(T) <= 16 { assert(nails >= 0 && nails < (size_of(T) * 8)) context.allocator = allocator diff --git a/core/math/cmplx/cmplx.odin b/core/math/cmplx/cmplx.odin index 4625f83c6..d1c70ca61 100644 --- a/core/math/cmplx/cmplx.odin +++ b/core/math/cmplx/cmplx.odin @@ -229,7 +229,7 @@ sqrt_complex128 :: proc "contextless" (x: complex128) -> complex128 { } ln_complex32 :: proc "contextless" (x: complex32) -> complex32 { - return complex(math.ln(abs(x)), phase(x)) + return complex32(ln_complex64(complex64(x))) } ln_complex64 :: proc "contextless" (x: complex64) -> complex64 { return complex(math.ln(abs(x)), phase(x)) @@ -240,26 +240,7 @@ ln_complex128 :: proc "contextless" (x: complex128) -> complex128 { exp_complex32 :: proc "contextless" (x: complex32) -> complex32 { - switch re, im := real(x), imag(x); { - case math.is_inf(re, 0): - switch { - case re > 0 && im == 0: - return x - case math.is_inf(im, 0) || math.is_nan(im): - if re < 0 { - return complex(0, math.copy_sign(0, im)) - } else { - return complex(math.inf_f64(1.0), math.nan_f64()) - } - } - case math.is_nan(re): - if im == 0 { - return complex(math.nan_f16(), im) - } - } - r := math.exp(real(x)) - s, c := math.sincos(imag(x)) - return complex(r*c, r*s) + return complex32(exp_complex64(complex64(x))) } exp_complex64 :: proc "contextless" (x: complex64) -> complex64 { switch re, im := real(x), imag(x); { @@ -308,37 +289,7 @@ exp_complex128 :: proc "contextless" (x: complex128) -> complex128 { pow_complex32 :: proc "contextless" (x, y: complex32) -> complex32 { - if x == 0 { // Guaranteed also true for x == -0. - if is_nan(y) { - return nan_complex32() - } - r, i := real(y), imag(y) - switch { - case r == 0: - return 1 - case r < 0: - if i == 0 { - return complex(math.inf_f16(1), 0) - } - return inf_complex32() - case r > 0: - return 0 - } - unreachable() - } - modulus := abs(x) - if modulus == 0 { - return complex(0, 0) - } - r := math.pow(modulus, real(y)) - arg := phase(x) - theta := real(y) * arg - if imag(y) != 0 { - r *= math.exp(-imag(y) * arg) - theta += imag(y) * math.ln(modulus) - } - s, c := math.sincos(theta) - return complex(r*c, r*s) + return complex32(pow_complex64(complex64(x), complex64(y))) } pow_complex64 :: proc "contextless" (x, y: complex64) -> complex64 { if x == 0 { // Guaranteed also true for x == -0. @@ -410,7 +361,7 @@ pow_complex128 :: proc "contextless" (x, y: complex128) -> complex128 { log10_complex32 :: proc "contextless" (x: complex32) -> complex32 { - return math.LN10*ln(x) + return complex32(log10_complex64(complex64(x))) } log10_complex64 :: proc "contextless" (x: complex64) -> complex64 { return math.LN10*ln(x) @@ -421,7 +372,7 @@ log10_complex128 :: proc "contextless" (x: complex128) -> complex128 { phase_complex32 :: proc "contextless" (x: complex32) -> f16 { - return math.atan2(imag(x), real(x)) + return f16(phase_complex64(complex64(x))) } phase_complex64 :: proc "contextless" (x: complex64) -> f32 { return math.atan2(imag(x), real(x)) @@ -432,8 +383,7 @@ phase_complex128 :: proc "contextless" (x: complex128) -> f64 { rect_complex32 :: proc "contextless" (r, θ: f16) -> complex32 { - s, c := math.sincos(θ) - return complex(r*c, r*s) + return complex32(rect_complex64(f32(r), f32(θ))) } rect_complex64 :: proc "contextless" (r, θ: f32) -> complex64 { s, c := math.sincos(θ) diff --git a/core/math/cmplx/cmplx_invtrig.odin b/core/math/cmplx/cmplx_invtrig.odin index b84f0ac9c..40a8493bc 100644 --- a/core/math/cmplx/cmplx_invtrig.odin +++ b/core/math/cmplx/cmplx_invtrig.odin @@ -61,8 +61,7 @@ atanh :: proc{ acos_complex32 :: proc "contextless" (x: complex32) -> complex32 { - w := asin(x) - return complex(math.PI/2 - real(w), -imag(w)) + return complex32(acos_complex64(complex64(x))) } acos_complex64 :: proc "contextless" (x: complex64) -> complex64 { w := asin(x) @@ -75,14 +74,7 @@ acos_complex128 :: proc "contextless" (x: complex128) -> complex128 { acosh_complex32 :: proc "contextless" (x: complex32) -> complex32 { - if x == 0 { - return complex(0, math.copy_sign(math.PI/2, imag(x))) - } - w := acos(x) - if imag(w) <= 0 { - return complex(-imag(w), real(w)) - } - return complex(imag(w), -real(w)) + return complex32(acosh_complex64(complex64(x))) } acosh_complex64 :: proc "contextless" (x: complex64) -> complex64 { if x == 0 { @@ -257,9 +249,7 @@ atan_complex128 :: proc "contextless" (x: complex128) -> complex128 { } atanh_complex32 :: proc "contextless" (x: complex32) -> complex32 { - z := complex(-imag(x), real(x)) // z = i * x - z = atan(z) - return complex(imag(z), -real(z)) // z = -i * z + return complex32(atanh_complex64(complex64(x))) } atanh_complex64 :: proc "contextless" (x: complex64) -> complex64 { z := complex(-imag(x), real(x)) // z = i * x diff --git a/core/math/cmplx/cmplx_trig.odin b/core/math/cmplx/cmplx_trig.odin index 7ca404fab..15e757506 100644 --- a/core/math/cmplx/cmplx_trig.odin +++ b/core/math/cmplx/cmplx_trig.odin @@ -350,7 +350,7 @@ _reduce_pi_f64 :: proc "contextless" (x: f64) -> f64 #no_bounds_check { // that is, 1/PI = SUM bdpi[i]*2^(-64*i). // 19 64-bit digits give 1216 bits of precision // to handle the largest possible f64 exponent. - @static bdpi := [?]u64{ + @(static, rodata) bdpi := [?]u64{ 0x0000000000000000, 0x517cc1b727220a94, 0xfe13abe8fa9a6ee0, diff --git a/core/math/fixed/fixed.odin b/core/math/fixed/fixed.odin index d55e24175..b23090307 100644 --- a/core/math/fixed/fixed.odin +++ b/core/math/fixed/fixed.odin @@ -41,7 +41,7 @@ init_from_f64 :: proc(x: ^$T/Fixed($Backing, $Fraction_Width), val: f64) { init_from_parts :: proc(x: ^$T/Fixed($Backing, $Fraction_Width), integer, fraction: Backing) { x.i = fraction x.i &= 1< f64 { diff --git a/core/math/linalg/general.odin b/core/math/linalg/general.odin index 51dfd2360..90fe7332c 100644 --- a/core/math/linalg/general.odin +++ b/core/math/linalg/general.odin @@ -3,6 +3,7 @@ package linalg import "core:math" import "base:builtin" import "base:intrinsics" +@require import "base:runtime" // Generic @@ -223,33 +224,27 @@ quaternion_mul_quaternion :: proc "contextless" (q1, q2: $Q) -> Q where IS_QUATE @(require_results) quaternion64_mul_vector3 :: proc "contextless" (q: $Q/quaternion64, v: $V/[3]$F/f16) -> V { - Raw_Quaternion :: struct {xyz: [3]f16, r: f16} - - q := transmute(Raw_Quaternion)q + q := transmute(runtime.Raw_Quaternion64_Vector_Scalar)q v := v - t := cross(2*q.xyz, v) - return V(v + q.r*t + cross(q.xyz, t)) + t := cross(2*q.vector, v) + return V(v + q.scalar*t + cross(q.vector, t)) } @(require_results) quaternion128_mul_vector3 :: proc "contextless" (q: $Q/quaternion128, v: $V/[3]$F/f32) -> V { - Raw_Quaternion :: struct {xyz: [3]f32, r: f32} - - q := transmute(Raw_Quaternion)q + q := transmute(runtime.Raw_Quaternion128_Vector_Scalar)q v := v - t := cross(2*q.xyz, v) - return V(v + q.r*t + cross(q.xyz, t)) + t := cross(2*q.vector, v) + return V(v + q.scalar*t + cross(q.vector, t)) } @(require_results) quaternion256_mul_vector3 :: proc "contextless" (q: $Q/quaternion256, v: $V/[3]$F/f64) -> V { - Raw_Quaternion :: struct {xyz: [3]f64, r: f64} - - q := transmute(Raw_Quaternion)q + q := transmute(runtime.Raw_Quaternion256_Vector_Scalar)q v := v - t := cross(2*q.xyz, v) - return V(v + q.r*t + cross(q.xyz, t)) + t := cross(2*q.vector, v) + return V(v + q.scalar*t + cross(q.vector, t)) } quaternion_mul_vector3 :: proc{quaternion64_mul_vector3, quaternion128_mul_vector3, quaternion256_mul_vector3} diff --git a/core/math/linalg/glsl/linalg_glsl.odin b/core/math/linalg/glsl/linalg_glsl.odin index bda1f1723..363a95887 100644 --- a/core/math/linalg/glsl/linalg_glsl.odin +++ b/core/math/linalg/glsl/linalg_glsl.odin @@ -1724,7 +1724,7 @@ quatFromMat4 :: proc "c" (m: mat4) -> (q: quat) { @(require_results) quatMulVec3 :: proc "c" (q: quat, v: vec3) -> vec3 { xyz := vec3{q.x, q.y, q.z} - t := cross(xyz, v) + t := cross(2.0 * xyz, v) return v + q.w*t + cross(xyz, t) } @@ -1832,7 +1832,7 @@ dquatFromDmat4 :: proc "c" (m: dmat4) -> (q: dquat) { @(require_results) dquatMulDvec3 :: proc "c" (q: dquat, v: dvec3) -> dvec3 { xyz := dvec3{q.x, q.y, q.z} - t := cross(xyz, v) + t := cross(2.0 * xyz, v) return v + q.w*t + cross(xyz, t) } diff --git a/core/math/linalg/specific.odin b/core/math/linalg/specific.odin index 41d0e5344..b841f0610 100644 --- a/core/math/linalg/specific.odin +++ b/core/math/linalg/specific.odin @@ -527,7 +527,7 @@ angle_from_quaternion :: proc{ @(require_results) axis_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> Vector3f16 { t1 := 1 - q.w*q.w - if t1 < 0 { + if t1 <= 0 { return {0, 0, 1} } t2 := 1.0 / math.sqrt(t1) @@ -536,7 +536,7 @@ axis_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> Vector3f16 @(require_results) axis_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> Vector3f32 { t1 := 1 - q.w*q.w - if t1 < 0 { + if t1 <= 0 { return {0, 0, 1} } t2 := 1.0 / math.sqrt(t1) @@ -545,7 +545,7 @@ axis_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> Vector3f32 @(require_results) axis_from_quaternion_f64 :: proc "contextless" (q: Quaternionf64) -> Vector3f64 { t1 := 1 - q.w*q.w - if t1 < 0 { + if t1 <= 0 { return {0, 0, 1} } t2 := 1.0 / math.sqrt(t1) diff --git a/core/math/linalg/specific_euler_angles_f16.odin b/core/math/linalg/specific_euler_angles_f16.odin index bacda163e..1e9ded9ab 100644 --- a/core/math/linalg/specific_euler_angles_f16.odin +++ b/core/math/linalg/specific_euler_angles_f16.odin @@ -159,7 +159,7 @@ roll_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> f16 { @(require_results) pitch_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> f16 { - y := 2 * (q.y*q.z + q.w*q.w) + y := 2 * (q.y*q.z + q.w*q.x) x := q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z if abs(x) <= F16_EPSILON && abs(y) <= F16_EPSILON { diff --git a/core/math/linalg/specific_euler_angles_f32.odin b/core/math/linalg/specific_euler_angles_f32.odin index b9957034f..e33b1f095 100644 --- a/core/math/linalg/specific_euler_angles_f32.odin +++ b/core/math/linalg/specific_euler_angles_f32.odin @@ -159,7 +159,7 @@ roll_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> f32 { @(require_results) pitch_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> f32 { - y := 2 * (q.y*q.z + q.w*q.w) + y := 2 * (q.y*q.z + q.w*q.x) x := q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z if abs(x) <= F32_EPSILON && abs(y) <= F32_EPSILON { diff --git a/core/math/linalg/specific_euler_angles_f64.odin b/core/math/linalg/specific_euler_angles_f64.odin index 8001d080a..9b5cf4b56 100644 --- a/core/math/linalg/specific_euler_angles_f64.odin +++ b/core/math/linalg/specific_euler_angles_f64.odin @@ -159,7 +159,7 @@ roll_from_quaternion_f64 :: proc "contextless" (q: Quaternionf64) -> f64 { @(require_results) pitch_from_quaternion_f64 :: proc "contextless" (q: Quaternionf64) -> f64 { - y := 2 * (q.y*q.z + q.w*q.w) + y := 2 * (q.y*q.z + q.w*q.x) x := q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z if abs(x) <= F64_EPSILON && abs(y) <= F64_EPSILON { diff --git a/core/math/math.odin b/core/math/math.odin index 8d85c2381..3d0ab3c4e 100644 --- a/core/math/math.odin +++ b/core/math/math.odin @@ -130,10 +130,10 @@ pow10 :: proc{ @(require_results) pow10_f16 :: proc "contextless" (n: f16) -> f16 { - @static pow10_pos_tab := [?]f16{ + @(static, rodata) pow10_pos_tab := [?]f16{ 1e00, 1e01, 1e02, 1e03, 1e04, } - @static pow10_neg_tab := [?]f16{ + @(static, rodata) pow10_neg_tab := [?]f16{ 1e-00, 1e-01, 1e-02, 1e-03, 1e-04, 1e-05, 1e-06, 1e-07, } @@ -151,13 +151,13 @@ pow10_f16 :: proc "contextless" (n: f16) -> f16 { @(require_results) pow10_f32 :: proc "contextless" (n: f32) -> f32 { - @static pow10_pos_tab := [?]f32{ + @(static, rodata) pow10_pos_tab := [?]f32{ 1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, } - @static pow10_neg_tab := [?]f32{ + @(static, rodata) pow10_neg_tab := [?]f32{ 1e-00, 1e-01, 1e-02, 1e-03, 1e-04, 1e-05, 1e-06, 1e-07, 1e-08, 1e-09, 1e-10, 1e-11, 1e-12, 1e-13, 1e-14, 1e-15, 1e-16, 1e-17, 1e-18, 1e-19, 1e-20, 1e-21, 1e-22, 1e-23, 1e-24, 1e-25, 1e-26, 1e-27, 1e-28, 1e-29, @@ -179,16 +179,16 @@ pow10_f32 :: proc "contextless" (n: f32) -> f32 { @(require_results) pow10_f64 :: proc "contextless" (n: f64) -> f64 { - @static pow10_tab := [?]f64{ + @(static, rodata) pow10_tab := [?]f64{ 1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30, 1e31, } - @static pow10_pos_tab32 := [?]f64{ + @(static, rodata) pow10_pos_tab32 := [?]f64{ 1e00, 1e32, 1e64, 1e96, 1e128, 1e160, 1e192, 1e224, 1e256, 1e288, } - @static pow10_neg_tab32 := [?]f64{ + @(static, rodata) pow10_neg_tab32 := [?]f64{ 1e-00, 1e-32, 1e-64, 1e-96, 1e-128, 1e-160, 1e-192, 1e-224, 1e-256, 1e-288, 1e-320, } @@ -1274,7 +1274,7 @@ binomial :: proc "contextless" (n, k: int) -> int { @(require_results) factorial :: proc "contextless" (n: int) -> int { when size_of(int) == size_of(i64) { - @static table := [21]int{ + @(static, rodata) table := [21]int{ 1, 1, 2, @@ -1298,7 +1298,7 @@ factorial :: proc "contextless" (n: int) -> int { 2_432_902_008_176_640_000, } } else { - @static table := [13]int{ + @(static, rodata) table := [13]int{ 1, 1, 2, diff --git a/core/math/math_gamma.odin b/core/math/math_gamma.odin index 00d4b7316..9f5a364d3 100644 --- a/core/math/math_gamma.odin +++ b/core/math/math_gamma.odin @@ -67,7 +67,7 @@ package math // masks any imprecision in the polynomial. @(private="file", require_results) stirling :: proc "contextless" (x: f64) -> (f64, f64) { - @(static) gamS := [?]f64{ + @(static, rodata) gamS := [?]f64{ +7.87311395793093628397e-04, -2.29549961613378126380e-04, -2.68132617805781232825e-03, @@ -103,7 +103,7 @@ gamma_f64 :: proc "contextless" (x: f64) -> f64 { return false } - @(static) gamP := [?]f64{ + @(static, rodata) gamP := [?]f64{ 1.60119522476751861407e-04, 1.19135147006586384913e-03, 1.04213797561761569935e-02, @@ -112,7 +112,7 @@ gamma_f64 :: proc "contextless" (x: f64) -> f64 { 4.94214826801497100753e-01, 9.99999999999999996796e-01, } - @(static) gamQ := [?]f64{ + @(static, rodata) gamQ := [?]f64{ -2.31581873324120129819e-05, +5.39605580493303397842e-04, -4.45641913851797240494e-03, diff --git a/core/math/math_lgamma.odin b/core/math/math_lgamma.odin index 0705d8564..828f17178 100644 --- a/core/math/math_lgamma.odin +++ b/core/math/math_lgamma.odin @@ -123,7 +123,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) { return -x } - @static lgamA := [?]f64{ + @(static, rodata) lgamA := [?]f64{ 0h3FB3C467E37DB0C8, 0h3FD4A34CC4A60FAD, 0h3FB13E001A5562A7, @@ -137,7 +137,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) { 0h3EFA7074428CFA52, 0h3F07858E90A45837, } - @static lgamR := [?]f64{ + @(static, rodata) lgamR := [?]f64{ 1.0, 0h3FF645A762C4AB74, 0h3FE71A1893D3DCDC, @@ -146,7 +146,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) { 0h3F497DDACA41A95B, 0h3EDEBAF7A5B38140, } - @static lgamS := [?]f64{ + @(static, rodata) lgamS := [?]f64{ 0hBFB3C467E37DB0C8, 0h3FCB848B36E20878, 0h3FD4D98F4F139F59, @@ -155,7 +155,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) { 0h3F5E26B67368F239, 0h3F00BFECDD17E945, } - @static lgamT := [?]f64{ + @(static, rodata) lgamT := [?]f64{ 0h3FDEF72BC8EE38A2, 0hBFC2E4278DC6C509, 0h3FB08B4294D5419B, @@ -172,7 +172,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) { 0hBF347F24ECC38C38, 0h3F35FD3EE8C2D3F4, } - @static lgamU := [?]f64{ + @(static, rodata) lgamU := [?]f64{ 0hBFB3C467E37DB0C8, 0h3FE4401E8B005DFF, 0h3FF7475CD119BD6F, @@ -180,7 +180,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) { 0h3FCD4EAEF6010924, 0h3F8B678BBF2BAB09, } - @static lgamV := [?]f64{ + @(static, rodata) lgamV := [?]f64{ 1.0, 0h4003A5D7C2BD619C, 0h40010725A42B18F5, @@ -188,7 +188,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) { 0h3FBAAE55D6537C88, 0h3F6A5ABB57D0CF61, } - @static lgamW := [?]f64{ + @(static, rodata) lgamW := [?]f64{ 0h3FDACFE390C97D69, 0h3FB555555555553B, 0hBF66C16C16B02E5C, diff --git a/core/math/math_sincos.odin b/core/math/math_sincos.odin index 578876ac5..b616f410d 100644 --- a/core/math/math_sincos.odin +++ b/core/math/math_sincos.odin @@ -234,7 +234,7 @@ _trig_reduce_f64 :: proc "contextless" (x: f64) -> (j: u64, z: f64) #no_bounds_c // that is, 4/pi = Sum bd_pi4[i]*2^(-64*i) // 19 64-bit digits and the leading one bit give 1217 bits // of precision to handle the largest possible f64 exponent. - @static bd_pi4 := [?]u64{ + @(static, rodata) bd_pi4 := [?]u64{ 0x0000000000000001, 0x45f306dc9c882a53, 0xf84eafa3ea69bb81, diff --git a/core/math/noise/internal.odin b/core/math/noise/internal.odin index 5837f9235..bd97bd45c 100644 --- a/core/math/noise/internal.odin +++ b/core/math/noise/internal.odin @@ -637,22 +637,20 @@ _internal_noise_4d_unskewed_base :: proc(seed: i64, coord: Vec4) -> (value: f32) // Next point is the closest vertex on the 4-simplex whose base vertex is the aforementioned vertex. score := 1.0 + ssi * (-1.0 / UNSKEW_4D) // Seems slightly faster than 1.0-xsi-ysi-zsi-wsi - if si.x >= si.x && si.x >= si.z && si.x >= si.w && si.x >= score { + switch { + case si.x >= si.x && si.x >= si.z && si.x >= si.w && si.x >= score: svp.x += PRIME_X si.x -= 1 ssi -= UNSKEW_4D - } - else if si.y > si.x && si.y >= si.z && si.y >= si.w && si.y >= score { + case si.y > si.x && si.y >= si.z && si.y >= si.w && si.y >= score: svp.y += PRIME_Y si.y -= 1 ssi -= UNSKEW_4D - } - else if si.z > si.x && si.z > si.y && si.z >= si.w && si.z >= score { + case si.z > si.x && si.z > si.y && si.z >= si.w && si.z >= score: svp.z += PRIME_Z si.z -= 1 ssi -= UNSKEW_4D - } - else if si.w > si.x && si.w > si.y && si.w > si.z && si.w >= score { + case si.w > si.x && si.w > si.y && si.w > si.z && si.w >= score: svp.w += PRIME_W si.w -= 1 ssi -= UNSKEW_4D @@ -690,7 +688,7 @@ _internal_noise_4d_unskewed_base :: proc(seed: i64, coord: Vec4) -> (value: f32) /* Utility functions */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") grad_2d :: proc(seed: i64, svp: [2]i64, delta: [2]f32) -> (value: f32) { hash := seed ~ svp.x ~ svp.y hash *= HASH_MULTIPLIER @@ -700,7 +698,7 @@ grad_2d :: proc(seed: i64, svp: [2]i64, delta: [2]f32) -> (value: f32) { return GRADIENTS_2D[gi] * delta.x + GRADIENTS_2D[gi | 1] * delta.y } -@(optimization_mode="speed") +@(optimization_mode="favor_size") grad_3d :: proc(seed: i64, rvp: [3]i64, delta: [3]f32) -> (value: f32) { hash := (seed ~ rvp.x) ~ (rvp.y ~ rvp.z) hash *= HASH_MULTIPLIER @@ -710,7 +708,7 @@ grad_3d :: proc(seed: i64, rvp: [3]i64, delta: [3]f32) -> (value: f32) { return GRADIENTS_3D[gi] * delta.x + GRADIENTS_3D[gi | 1] * delta.y + GRADIENTS_3D[gi | 2] * delta.z } -@(optimization_mode="speed") +@(optimization_mode="favor_size") grad_4d :: proc(seed: i64, svp: [4]i64, delta: [4]f32) -> (value: f32) { hash := seed ~ (svp.x ~ svp.y) ~ (svp.z ~ svp.w) hash *= HASH_MULTIPLIER @@ -722,13 +720,13 @@ grad_4d :: proc(seed: i64, svp: [4]i64, delta: [4]f32) -> (value: f32) { grad :: proc {grad_2d, grad_3d, grad_4d} -@(optimization_mode="speed") +@(optimization_mode="favor_size") fast_floor :: proc(x: f64) -> (floored: i64) { xi := i64(x) return x < f64(xi) ? xi - 1 : xi } -@(optimization_mode="speed") +@(optimization_mode="favor_size") fast_round :: proc(x: f64) -> (rounded: i64) { return x < 0 ? i64(x - 0.5) : i64(x + 0.5) -} \ No newline at end of file +} diff --git a/core/math/rand/distributions.odin b/core/math/rand/distributions.odin index 9365e8b76..755e6f86f 100644 --- a/core/math/rand/distributions.odin +++ b/core/math/rand/distributions.odin @@ -8,12 +8,12 @@ float32_uniform :: float32_range // Triangular Distribution // See: http://wikipedia.org/wiki/Triangular_distribution @(require_results) -float64_triangular :: proc(lo, hi: f64, mode: Maybe(f64), r: ^Rand = nil) -> f64 { +float64_triangular :: proc(lo, hi: f64, mode: Maybe(f64), gen := context.random_generator) -> f64 { if hi-lo == 0 { return lo } lo, hi := lo, hi - u := float64(r) + u := float64(gen) c := f64(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1) if u > c { u = 1-u @@ -26,12 +26,12 @@ float64_triangular :: proc(lo, hi: f64, mode: Maybe(f64), r: ^Rand = nil) -> f64 // Triangular Distribution // See: http://wikipedia.org/wiki/Triangular_distribution @(require_results) -float32_triangular :: proc(lo, hi: f32, mode: Maybe(f32), r: ^Rand = nil) -> f32 { +float32_triangular :: proc(lo, hi: f32, mode: Maybe(f32), gen := context.random_generator) -> f32 { if hi-lo == 0 { return lo } lo, hi := lo, hi - u := float32(r) + u := float32(gen) c := f32(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1) if u > c { u = 1-u @@ -44,25 +44,25 @@ float32_triangular :: proc(lo, hi: f32, mode: Maybe(f32), r: ^Rand = nil) -> f32 // Normal/Gaussian Distribution @(require_results) -float64_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 { - return norm_float64(r) * stddev + mean +float64_normal :: proc(mean, stddev: f64, gen := context.random_generator) -> f64 { + return norm_float64(gen) * stddev + mean } // Normal/Gaussian Distribution @(require_results) -float32_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 { - return f32(float64_normal(f64(mean), f64(stddev), r)) +float32_normal :: proc(mean, stddev: f32, gen := context.random_generator) -> f32 { + return f32(float64_normal(f64(mean), f64(stddev), gen)) } // Log Normal Distribution @(require_results) -float64_log_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 { - return math.exp(float64_normal(mean, stddev, r)) +float64_log_normal :: proc(mean, stddev: f64, gen := context.random_generator) -> f64 { + return math.exp(float64_normal(mean, stddev, gen)) } // Log Normal Distribution @(require_results) -float32_log_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 { - return f32(float64_log_normal(f64(mean), f64(stddev), r)) +float32_log_normal :: proc(mean, stddev: f32, gen := context.random_generator) -> f32 { + return f32(float64_log_normal(f64(mean), f64(stddev), gen)) } @@ -72,8 +72,8 @@ float32_log_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 { // 0 to positive infinity if lambda > 0 // negative infinity to 0 if lambda <= 0 @(require_results) -float64_exponential :: proc(lambda: f64, r: ^Rand = nil) -> f64 { - return - math.ln(1 - float64(r)) / lambda +float64_exponential :: proc(lambda: f64, gen := context.random_generator) -> f64 { + return - math.ln(1 - float64(gen)) / lambda } // Exponential Distribution // `lambda` is 1.0/(desired mean). It should be non-zero. @@ -81,8 +81,8 @@ float64_exponential :: proc(lambda: f64, r: ^Rand = nil) -> f64 { // 0 to positive infinity if lambda > 0 // negative infinity to 0 if lambda <= 0 @(require_results) -float32_exponential :: proc(lambda: f32, r: ^Rand = nil) -> f32 { - return f32(float64_exponential(f64(lambda), r)) +float32_exponential :: proc(lambda: f32, gen := context.random_generator) -> f32 { + return f32(float64_exponential(f64(lambda), gen)) } @@ -96,7 +96,7 @@ float32_exponential :: proc(lambda: f32, r: ^Rand = nil) -> f32 { // // mean is alpha*beta, variance is math.pow(alpha*beta, 2) @(require_results) -float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { +float64_gamma :: proc(alpha, beta: f64, gen := context.random_generator) -> f64 { if alpha <= 0 || beta <= 0 { panic(#procedure + ": alpha and beta must be > 0.0") } @@ -112,11 +112,11 @@ float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { bbb := alpha - LOG4 ccc := alpha + ainv for { - u1 := float64(r) + u1 := float64(gen) if !(1e-7 < u1 && u1 < 0.9999999) { continue } - u2 := 1 - float64(r) + u2 := 1 - float64(gen) v := math.ln(u1 / (1 - u1)) / ainv x := alpha * math.exp(v) z := u1 * u1 * u2 @@ -127,12 +127,12 @@ float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { } case alpha == 1: // float64_exponential(1/beta) - return -math.ln(1 - float64(r)) * beta + return -math.ln(1 - float64(gen)) * beta case: // ALGORITHM GS of Statistical Computing - Kennedy & Gentle x: f64 for { - u := float64(r) + u := float64(gen) b := (math.e + alpha) / math.e p := b * u if p <= 1 { @@ -140,7 +140,7 @@ float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { } else { x = -math.ln((b - p) / alpha) } - u1 := float64(r) + u1 := float64(gen) if p > 1 { if u1 <= math.pow(x, alpha-1) { break @@ -162,8 +162,8 @@ float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { // // mean is alpha*beta, variance is math.pow(alpha*beta, 2) @(require_results) -float32_gamma :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { - return f32(float64_gamma(f64(alpha), f64(beta), r)) +float32_gamma :: proc(alpha, beta: f32, gen := context.random_generator) -> f32 { + return f32(float64_gamma(f64(alpha), f64(beta), gen)) } @@ -173,14 +173,14 @@ float32_gamma :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { // // Return values range between 0 and 1 @(require_results) -float64_beta :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { +float64_beta :: proc(alpha, beta: f64, gen := context.random_generator) -> f64 { if alpha <= 0 || beta <= 0 { panic(#procedure + ": alpha and beta must be > 0.0") } // Knuth Vol 2 Ed 3 pg 134 "the beta distribution" - y := float64_gamma(alpha, 1.0, r) + y := float64_gamma(alpha, 1.0, gen) if y != 0 { - return y / (y + float64_gamma(beta, 1.0, r)) + return y / (y + float64_gamma(beta, 1.0, gen)) } return 0 } @@ -190,35 +190,35 @@ float64_beta :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { // // Return values range between 0 and 1 @(require_results) -float32_beta :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { - return f32(float64_beta(f64(alpha), f64(beta), r)) +float32_beta :: proc(alpha, beta: f32, gen := context.random_generator) -> f32 { + return f32(float64_beta(f64(alpha), f64(beta), gen)) } // Pareto distribution, `alpha` is the shape parameter. // https://wikipedia.org/wiki/Pareto_distribution @(require_results) -float64_pareto :: proc(alpha: f64, r: ^Rand = nil) -> f64 { - return math.pow(1 - float64(r), -1.0 / alpha) +float64_pareto :: proc(alpha: f64, gen := context.random_generator) -> f64 { + return math.pow(1 - float64(gen), -1.0 / alpha) } // Pareto distribution, `alpha` is the shape parameter. // https://wikipedia.org/wiki/Pareto_distribution @(require_results) -float32_pareto :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { - return f32(float64_pareto(f64(alpha), r)) +float32_pareto :: proc(alpha, beta: f32, gen := context.random_generator) -> f32 { + return f32(float64_pareto(f64(alpha), gen)) } // Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter. @(require_results) -float64_weibull :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { - u := 1 - float64(r) +float64_weibull :: proc(alpha, beta: f64, gen := context.random_generator) -> f64 { + u := 1 - float64(gen) return alpha * math.pow(-math.ln(u), 1.0/beta) } // Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter. @(require_results) -float32_weibull :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { - return f32(float64_weibull(f64(alpha), f64(beta), r)) +float32_weibull :: proc(alpha, beta: f32, gen := context.random_generator) -> f32 { + return f32(float64_weibull(f64(alpha), f64(beta), gen)) } @@ -227,23 +227,23 @@ float32_weibull :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { // `kappa` is the concentration parameter which must be >= 0 // When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi @(require_results) -float64_von_mises :: proc(mean_angle, kappa: f64, r: ^Rand = nil) -> f64 { +float64_von_mises :: proc(mean_angle, kappa: f64, gen := context.random_generator) -> f64 { // Fisher, N.I., "Statistical Analysis of Circular Data", Cambridge University Press, 1993. mu := mean_angle if kappa <= 1e-6 { - return math.TAU * float64(r) + return math.TAU * float64(gen) } s := 0.5 / kappa t := s + math.sqrt(1 + s*s) z: f64 for { - u1 := float64(r) + u1 := float64(gen) z = math.cos(math.TAU * 0.5 * u1) d := z / (t + z) - u2 := float64(r) + u2 := float64(gen) if u2 < 1 - d*d || u2 <= (1-d)*math.exp(d) { break } @@ -251,7 +251,7 @@ float64_von_mises :: proc(mean_angle, kappa: f64, r: ^Rand = nil) -> f64 { q := 1.0 / t f := (q + z) / (1 + q*z) - u3 := float64(r) + u3 := float64(gen) if u3 > 0.5 { return math.mod(mu + math.acos(f), math.TAU) } else { @@ -263,57 +263,57 @@ float64_von_mises :: proc(mean_angle, kappa: f64, r: ^Rand = nil) -> f64 { // `kappa` is the concentration parameter which must be >= 0 // When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi @(require_results) -float32_von_mises :: proc(mean_angle, kappa: f32, r: ^Rand = nil) -> f32 { - return f32(float64_von_mises(f64(mean_angle), f64(kappa), r)) +float32_von_mises :: proc(mean_angle, kappa: f32, gen := context.random_generator) -> f32 { + return f32(float64_von_mises(f64(mean_angle), f64(kappa), gen)) } // Cauchy-Lorentz Distribution // `x_0` is the location, `gamma` is the scale where `gamma` > 0 @(require_results) -float64_cauchy_lorentz :: proc(x_0, gamma: f64, r: ^Rand = nil) -> f64 { +float64_cauchy_lorentz :: proc(x_0, gamma: f64, gen := context.random_generator) -> f64 { assert(gamma > 0) // Calculated from the inverse CDF - return math.tan(math.PI * (float64(r) - 0.5))*gamma + x_0 + return math.tan(math.PI * (float64(gen) - 0.5))*gamma + x_0 } // Cauchy-Lorentz Distribution // `x_0` is the location, `gamma` is the scale where `gamma` > 0 @(require_results) -float32_cauchy_lorentz :: proc(x_0, gamma: f32, r: ^Rand = nil) -> f32 { - return f32(float64_cauchy_lorentz(f64(x_0), f64(gamma), r)) +float32_cauchy_lorentz :: proc(x_0, gamma: f32, gen := context.random_generator) -> f32 { + return f32(float64_cauchy_lorentz(f64(x_0), f64(gamma), gen)) } // Log Cauchy-Lorentz Distribution // `x_0` is the location, `gamma` is the scale where `gamma` > 0 @(require_results) -float64_log_cauchy_lorentz :: proc(x_0, gamma: f64, r: ^Rand = nil) -> f64 { +float64_log_cauchy_lorentz :: proc(x_0, gamma: f64, gen := context.random_generator) -> f64 { assert(gamma > 0) - return math.exp(math.tan(math.PI * (float64(r) - 0.5))*gamma + x_0) + return math.exp(math.tan(math.PI * (float64(gen) - 0.5))*gamma + x_0) } // Log Cauchy-Lorentz Distribution // `x_0` is the location, `gamma` is the scale where `gamma` > 0 @(require_results) -float32_log_cauchy_lorentz :: proc(x_0, gamma: f32, r: ^Rand = nil) -> f32 { - return f32(float64_log_cauchy_lorentz(f64(x_0), f64(gamma), r)) +float32_log_cauchy_lorentz :: proc(x_0, gamma: f32, gen := context.random_generator) -> f32 { + return f32(float64_log_cauchy_lorentz(f64(x_0), f64(gamma), gen)) } // Laplace Distribution // `b` is the scale where `b` > 0 @(require_results) -float64_laplace :: proc(mean, b: f64, r: ^Rand = nil) -> f64 { +float64_laplace :: proc(mean, b: f64, gen := context.random_generator) -> f64 { assert(b > 0) - p := float64(r)-0.5 + p := float64(gen)-0.5 return -math.sign(p)*math.ln(1 - 2*abs(p))*b + mean } // Laplace Distribution // `b` is the scale where `b` > 0 @(require_results) -float32_laplace :: proc(mean, b: f32, r: ^Rand = nil) -> f32 { - return f32(float64_laplace(f64(mean), f64(b), r)) +float32_laplace :: proc(mean, b: f32, gen := context.random_generator) -> f32 { + return f32(float64_laplace(f64(mean), f64(b), gen)) } @@ -321,18 +321,18 @@ float32_laplace :: proc(mean, b: f32, r: ^Rand = nil) -> f32 { // `eta` is the shape, `b` is the scale // Both `eta` and `b` must be > 0 @(require_results) -float64_gompertz :: proc(eta, b: f64, r: ^Rand = nil) -> f64 { +float64_gompertz :: proc(eta, b: f64, gen := context.random_generator) -> f64 { if eta <= 0 || b <= 0 { panic(#procedure + ": eta and b must be > 0.0") } - p := float64(r) + p := float64(gen) return math.ln(1 - math.ln(1 - p)/eta)/b } // Gompertz Distribution // `eta` is the shape, `b` is the scale // Both `eta` and `b` must be > 0 @(require_results) -float32_gompertz :: proc(eta, b: f32, r: ^Rand = nil) -> f32 { - return f32(float64_gompertz(f64(eta), f64(b), r)) +float32_gompertz :: proc(eta, b: f32, gen := context.random_generator) -> f32 { + return f32(float64_gompertz(f64(eta), f64(b), gen)) } diff --git a/core/math/rand/exp.odin b/core/math/rand/exp.odin index 719debe75..4ceb750da 100644 --- a/core/math/rand/exp.odin +++ b/core/math/rand/exp.odin @@ -16,10 +16,10 @@ import "core:math" // https://www.jstatsoft.org/article/view/v005i08 [web page] // @(require_results) -exp_float64 :: proc(r: ^Rand = nil) -> f64 { +exp_float64 :: proc(gen := context.random_generator) -> f64 { re :: 7.69711747013104972 - @(static) + @(static, rodata) ke := [256]u32{ 0xe290a139, 0x0, 0x9beadebc, 0xc377ac71, 0xd4ddb990, 0xde893fb8, 0xe4a8e87c, 0xe8dff16a, 0xebf2deab, 0xee49a6e8, @@ -74,7 +74,7 @@ exp_float64 :: proc(r: ^Rand = nil) -> f64 { 0xf7b577d2, 0xf69c650c, 0xf51530f0, 0xf2cb0e3c, 0xeeefb15d, 0xe6da6ecf, } - @(static) + @(static, rodata) we := [256]f32{ 2.0249555e-09, 1.486674e-11, 2.4409617e-11, 3.1968806e-11, 3.844677e-11, 4.4228204e-11, 4.9516443e-11, 5.443359e-11, @@ -141,7 +141,7 @@ exp_float64 :: proc(r: ^Rand = nil) -> f64 { 1.2393786e-09, 1.276585e-09, 1.3193139e-09, 1.3695435e-09, 1.4305498e-09, 1.508365e-09, 1.6160854e-09, 1.7921248e-09, } - @(static) + @(static, rodata) fe := [256]f32{ 1, 0.9381437, 0.90046996, 0.87170434, 0.8477855, 0.8269933, 0.8084217, 0.7915276, 0.77595687, 0.7614634, 0.7478686, @@ -199,16 +199,16 @@ exp_float64 :: proc(r: ^Rand = nil) -> f64 { } for { - j := uint32(r) + j := uint32(gen) i := j & 0xFF x := f64(j) * f64(we[i]) if j < ke[i] { return x } if i == 0 { - return re - math.ln(float64(r)) + return re - math.ln(float64(gen)) } - if fe[i]+f32(float64(r))*(fe[i-1]-fe[i]) < f32(math.exp(-x)) { + if fe[i]+f32(float64(gen))*(fe[i-1]-fe[i]) < f32(math.exp(-x)) { return x } } diff --git a/core/math/rand/normal.odin b/core/math/rand/normal.odin index f96163fe9..bc566344c 100644 --- a/core/math/rand/normal.odin +++ b/core/math/rand/normal.odin @@ -18,10 +18,10 @@ import "core:math" // https://www.jstatsoft.org/article/view/v005i08 [web page] // @(require_results) -norm_float64 :: proc(r: ^Rand = nil) -> f64 { +norm_float64 :: proc(gen := context.random_generator) -> f64 { rn :: 3.442619855899 - @(static) + @(static, rodata) kn := [128]u32{ 0x76ad2212, 0x00000000, 0x600f1b53, 0x6ce447a6, 0x725b46a2, 0x7560051d, 0x774921eb, 0x789a25bd, 0x799045c3, 0x7a4bce5d, @@ -50,7 +50,7 @@ norm_float64 :: proc(r: ^Rand = nil) -> f64 { 0x7da61a1e, 0x7d72a0fb, 0x7d30e097, 0x7cd9b4ab, 0x7c600f1a, 0x7ba90bdc, 0x7a722176, 0x77d664e5, } - @(static) + @(static, rodata) wn := [128]f32{ 1.7290405e-09, 1.2680929e-10, 1.6897518e-10, 1.9862688e-10, 2.2232431e-10, 2.4244937e-10, 2.601613e-10, 2.7611988e-10, @@ -85,7 +85,7 @@ norm_float64 :: proc(r: ^Rand = nil) -> f64 { 1.2601323e-09, 1.2857697e-09, 1.3146202e-09, 1.347784e-09, 1.3870636e-09, 1.4357403e-09, 1.5008659e-09, 1.6030948e-09, } - @(static) + @(static, rodata) fn := [128]f32{ 1.00000000, 0.9635997, 0.9362827, 0.9130436, 0.89228165, 0.87324303, 0.8555006, 0.8387836, 0.8229072, 0.8077383, @@ -115,15 +115,8 @@ norm_float64 :: proc(r: ^Rand = nil) -> f64 { 0.008624485, 0.005548995, 0.0026696292, } - r := r - if r == nil { - // NOTE(bill, 2020-09-07): Do this so that people can - // enforce the global random state if necessary with `nil` - r = &global_rand - } - for { - j := i32(uint32(r)) + j := i32(uint32(gen)) i := j & 0x7f x := f64(j) * f64(wn[i]) if u32(abs(j)) < kn[i] { @@ -133,15 +126,15 @@ norm_float64 :: proc(r: ^Rand = nil) -> f64 { if i == 0 { for { - x = -math.ln(float64(r)) * (1.0/ rn) - y := -math.ln(float64(r)) + x = -math.ln(float64(gen)) * (1.0/ rn) + y := -math.ln(float64(gen)) if y+y >= x*x { break } } return j > 0 ? rn + x : -rn - x } - if fn[i]+f32(float64(r))*(fn[i-1]-fn[i]) < f32(math.exp(-0.5*x*x)) { + if fn[i]+f32(float64(gen))*(fn[i-1]-fn[i]) < f32(math.exp(-0.5*x*x)) { return x } } diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index d6a20bd1e..e02f3db80 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -5,21 +5,26 @@ Package core:math/rand implements various random number generators package rand import "base:intrinsics" +import "base:runtime" import "core:math" import "core:mem" -Rand :: struct { - state: u64, - inc: u64, - is_system: bool, +Generator :: runtime.Random_Generator + +Generator_Query_Info :: runtime.Random_Generator_Query_Info + +Default_Random_State :: runtime.Default_Random_State +default_random_generator :: runtime.default_random_generator + +create :: proc(seed: u64) -> (state: Default_Random_State) { + seed := seed + runtime.default_random_generator(&state) + runtime.default_random_generator_proc(&state, .Reset, ([^]byte)(&seed)[:size_of(seed)]) + return } - -@(private) -global_rand := create(u64(intrinsics.read_cycle_counter())) - /* -Sets the seed used by the global random number generator. +Reset the seed used by the context.random_generator. Inputs: - seed: The seed value @@ -36,137 +41,55 @@ Example: Possible Output: 10 - */ +@(deprecated="Prefer `rand.reset`") set_global_seed :: proc(seed: u64) { - init(&global_rand, seed) + runtime.random_generator_reset_u64(context.random_generator, seed) } /* -Creates a new random number generator. +Reset the seed used by the context.random_generator. Inputs: -- seed: The seed value to create the random number generator with - -Returns: -- res: The created random number generator +- seed: The seed value Example: import "core:math/rand" import "core:fmt" - create_example :: proc() { - my_rand := rand.create(1) - fmt.println(rand.uint64(&my_rand)) + set_global_seed_example :: proc() { + rand.set_global_seed(1) + fmt.println(rand.uint64()) } Possible Output: 10 - */ -@(require_results) -create :: proc(seed: u64) -> (res: Rand) { - r: Rand - init(&r, seed) - return r +reset :: proc(seed: u64, gen := context.random_generator) { + runtime.random_generator_reset_u64(gen, seed) } -/* -Initialises a random number generator. - -Inputs: -- r: The random number generator to initialise -- seed: The seed value to initialise this random number generator - -Example: - import "core:math/rand" - import "core:fmt" - - init_example :: proc() { - my_rand: rand.Rand - rand.init(&my_rand, 1) - fmt.println(rand.uint64(&my_rand)) - } - -Possible Output: - - 10 - -*/ -init :: proc(r: ^Rand, seed: u64) { - r.state = 0 - r.inc = (seed << 1) | 1 - _random_u64(r) - r.state += seed - _random_u64(r) +reset_bytes :: proc(bytes: []byte, gen := context.random_generator) { + runtime.random_generator_reset_bytes(gen, bytes) } -/* -Initialises a random number generator to use the system random number generator. -The system random number generator is platform specific. -On `linux` refer to the `getrandom` syscall. -On `darwin` refer to `getentropy`. -On `windows` refer to `BCryptGenRandom`. - -All other platforms are not supported - -Inputs: -- r: The random number generator to use the system random number generator - -WARNING: Panics if the system is not either `windows`, `darwin` or `linux` - -Example: - import "core:math/rand" - import "core:fmt" - - init_as_system_example :: proc() { - my_rand: rand.Rand - rand.init_as_system(&my_rand) - fmt.println(rand.uint64(&my_rand)) - } - -Possible Output: - - 10 - -*/ -init_as_system :: proc(r: ^Rand) { - if !#defined(_system_random) { - panic(#procedure + " is not supported on this platform yet") - } - r.state = 0 - r.inc = 0 - r.is_system = true +query_info :: proc(gen := context.random_generator) -> Generator_Query_Info { + return runtime.random_generator_query_info(gen) } + @(private) -_random_u64 :: proc(r: ^Rand) -> u64 { - r := r - if r == nil { - r = &global_rand - } - when #defined(_system_random) { - if r.is_system { - return _system_random() - } - } - - - old_state := r.state - r.state = old_state * 6364136223846793005 + (r.inc|1) - xor_shifted := (((old_state >> 59) + 5) ~ old_state) * 12605985483714917081 - rot := (old_state >> 59) - return (xor_shifted >> rot) | (xor_shifted << ((-rot) & 63)) +_random_u64 :: proc(gen := context.random_generator) -> (res: u64) { + ok := runtime.random_generator_read_ptr(gen, &res, size_of(res)) + assert(ok, "uninitialized gen/context.random_generator") + return } /* Generates a random 32 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. -Inputs: -- r: The random number generator to use, or nil for the global generator - Returns: - val: A random unsigned 32 bit value @@ -175,11 +98,7 @@ Example: import "core:fmt" uint32_example :: proc() { - // Using the global random number generator fmt.println(rand.uint32()) - // Using local random number generator - my_rand := rand.create(1) - fmt.println(rand.uint32(&my_rand)) } Possible Output: @@ -189,14 +108,11 @@ Possible Output: */ @(require_results) -uint32 :: proc(r: ^Rand = nil) -> (val: u32) { return u32(_random_u64(r)) } +uint32 :: proc(gen := context.random_generator) -> (val: u32) { return u32(_random_u64(gen)) } /* Generates a random 64 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. -Inputs: -- r: The random number generator to use, or nil for the global generator - Returns: - val: A random unsigned 64 bit value @@ -205,11 +121,7 @@ Example: import "core:fmt" uint64_example :: proc() { - // Using the global random number generator fmt.println(rand.uint64()) - // Using local random number generator - my_rand := rand.create(1) - fmt.println(rand.uint64(&my_rand)) } Possible Output: @@ -219,14 +131,11 @@ Possible Output: */ @(require_results) -uint64 :: proc(r: ^Rand = nil) -> (val: u64) { return _random_u64(r) } +uint64 :: proc(gen := context.random_generator) -> (val: u64) { return _random_u64(gen) } /* Generates a random 128 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. -Inputs: -- r: The random number generator to use, or nil for the global generator - Returns: - val: A random unsigned 128 bit value @@ -235,11 +144,7 @@ Example: import "core:fmt" uint128_example :: proc() { - // Using the global random number generator fmt.println(rand.uint128()) - // Using local random number generator - my_rand := rand.create(1) - fmt.println(rand.uint128(&my_rand)) } Possible Output: @@ -249,9 +154,9 @@ Possible Output: */ @(require_results) -uint128 :: proc(r: ^Rand = nil) -> (val: u128) { - a := u128(_random_u64(r)) - b := u128(_random_u64(r)) +uint128 :: proc(gen := context.random_generator) -> (val: u128) { + a := u128(_random_u64(gen)) + b := u128(_random_u64(gen)) return (a<<64) | b } @@ -259,9 +164,6 @@ uint128 :: proc(r: ^Rand = nil) -> (val: u128) { Generates a random 31 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. The sign bit will always be set to 0, thus all generated numbers will be positive. -Inputs: -- r: The random number generator to use, or nil for the global generator - Returns: - val: A random 31 bit value @@ -270,11 +172,7 @@ Example: import "core:fmt" int31_example :: proc() { - // Using the global random number generator fmt.println(rand.int31()) - // Using local random number generator - my_rand := rand.create(1) - fmt.println(rand.int31(&my_rand)) } Possible Output: @@ -283,15 +181,12 @@ Possible Output: 389 */ -@(require_results) int31 :: proc(r: ^Rand = nil) -> (val: i32) { return i32(uint32(r) << 1 >> 1) } +@(require_results) int31 :: proc(gen := context.random_generator) -> (val: i32) { return i32(uint32(gen) << 1 >> 1) } /* Generates a random 63 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. The sign bit will always be set to 0, thus all generated numbers will be positive. -Inputs: -- r: The random number generator to use, or nil for the global generator - Returns: - val: A random 63 bit value @@ -300,11 +195,7 @@ Example: import "core:fmt" int63_example :: proc() { - // Using the global random number generator fmt.println(rand.int63()) - // Using local random number generator - my_rand := rand.create(1) - fmt.println(rand.int63(&my_rand)) } Possible Output: @@ -313,15 +204,12 @@ Possible Output: 389 */ -@(require_results) int63 :: proc(r: ^Rand = nil) -> (val: i64) { return i64(uint64(r) << 1 >> 1) } +@(require_results) int63 :: proc(gen := context.random_generator) -> (val: i64) { return i64(uint64(gen) << 1 >> 1) } /* Generates a random 127 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. The sign bit will always be set to 0, thus all generated numbers will be positive. -Inputs: -- r: The random number generator to use, or nil for the global generator - Returns: - val: A random 127 bit value @@ -330,11 +218,7 @@ Example: import "core:fmt" int127_example :: proc() { - // Using the global random number generator fmt.println(rand.int127()) - // Using local random number generator - my_rand := rand.create(1) - fmt.println(rand.int127(&my_rand)) } Possible Output: @@ -343,14 +227,13 @@ Possible Output: 389 */ -@(require_results) int127 :: proc(r: ^Rand = nil) -> (val: i128) { return i128(uint128(r) << 1 >> 1) } +@(require_results) int127 :: proc(gen := context.random_generator) -> (val: i128) { return i128(uint128(gen) << 1 >> 1) } /* Generates a random 31 bit value in the range `[0, n)` using the provided random number generator. If no generator is provided the global random number generator will be used. Inputs: - n: The upper bound of the generated number, this value is exclusive -- r: The random number generator to use, or nil for the global generator Returns: - val: A random 31 bit value in the range `[0, n)` @@ -362,11 +245,7 @@ Example: import "core:fmt" int31_max_example :: proc() { - // Using the global random number generator fmt.println(rand.int31_max(16)) - // Using local random number generator - my_rand := rand.create(1) - fmt.println(rand.int31_max(1024, &my_rand)) } Possible Output: @@ -376,17 +255,17 @@ Possible Output: */ @(require_results) -int31_max :: proc(n: i32, r: ^Rand = nil) -> (val: i32) { +int31_max :: proc(n: i32, gen := context.random_generator) -> (val: i32) { if n <= 0 { panic("Invalid argument to int31_max") } if n&(n-1) == 0 { - return int31(r) & (n-1) + return int31(gen) & (n-1) } max := i32((1<<31) - 1 - (1<<31)%u32(n)) - v := int31(r) + v := int31(gen) for v > max { - v = int31(r) + v = int31(gen) } return v % n } @@ -396,7 +275,6 @@ Generates a random 63 bit value in the range `[0, n)` using the provided random Inputs: - n: The upper bound of the generated number, this value is exclusive -- r: The random number generator to use, or nil for the global generator Returns: - val: A random 63 bit value in the range `[0, n)` @@ -408,11 +286,7 @@ Example: import "core:fmt" int63_max_example :: proc() { - // Using the global random number generator fmt.println(rand.int63_max(16)) - // Using local random number generator - my_rand := rand.create(1) - fmt.println(rand.int63_max(1024, &my_rand)) } Possible Output: @@ -422,17 +296,17 @@ Possible Output: */ @(require_results) -int63_max :: proc(n: i64, r: ^Rand = nil) -> (val: i64) { +int63_max :: proc(n: i64, gen := context.random_generator) -> (val: i64) { if n <= 0 { panic("Invalid argument to int63_max") } if n&(n-1) == 0 { - return int63(r) & (n-1) + return int63(gen) & (n-1) } max := i64((1<<63) - 1 - (1<<63)%u64(n)) - v := int63(r) + v := int63(gen) for v > max { - v = int63(r) + v = int63(gen) } return v % n } @@ -442,7 +316,6 @@ Generates a random 127 bit value in the range `[0, n)` using the provided random Inputs: - n: The upper bound of the generated number, this value is exclusive -- r: The random number generator to use, or nil for the global generator Returns: - val: A random 127 bit value in the range `[0, n)` @@ -454,11 +327,7 @@ Example: import "core:fmt" int127_max_example :: proc() { - // Using the global random number generator fmt.println(rand.int127_max(16)) - // Using local random number generator - my_rand := rand.create(1) - fmt.println(rand.int127_max(1024, &my_rand)) } Possible Output: @@ -468,17 +337,17 @@ Possible Output: */ @(require_results) -int127_max :: proc(n: i128, r: ^Rand = nil) -> (val: i128) { +int127_max :: proc(n: i128, gen := context.random_generator) -> (val: i128) { if n <= 0 { panic("Invalid argument to int127_max") } if n&(n-1) == 0 { - return int127(r) & (n-1) + return int127(gen) & (n-1) } max := i128((1<<127) - 1 - (1<<127)%u128(n)) - v := int127(r) + v := int127(gen) for v > max { - v = int127(r) + v = int127(gen) } return v % n } @@ -488,7 +357,6 @@ Generates a random integer value in the range `[0, n)` using the provided random Inputs: - n: The upper bound of the generated number, this value is exclusive -- r: The random number generator to use, or nil for the global generator Returns: - val: A random integer value in the range `[0, n)` @@ -500,11 +368,7 @@ Example: import "core:fmt" int_max_example :: proc() { - // Using the global random number generator fmt.println(rand.int_max(16)) - // Using local random number generator - my_rand := rand.create(1) - fmt.println(rand.int_max(1024, &my_rand)) } Possible Output: @@ -514,23 +378,20 @@ Possible Output: */ @(require_results) -int_max :: proc(n: int, r: ^Rand = nil) -> (val: int) { +int_max :: proc(n: int, gen := context.random_generator) -> (val: int) { if n <= 0 { panic("Invalid argument to int_max") } when size_of(int) == 4 { - return int(int31_max(i32(n), r)) + return int(int31_max(i32(n), gen)) } else { - return int(int63_max(i64(n), r)) + return int(int63_max(i64(n), gen)) } } /* Generates a random double floating point value in the range `[0, 1)` using the provided random number generator. If no generator is provided the global random number generator will be used. -Inputs: -- r: The random number generator to use, or nil for the global generator - Returns: - val: A random double floating point value in the range `[0, 1)` @@ -539,11 +400,7 @@ Example: import "core:fmt" float64_example :: proc() { - // Using the global random number generator fmt.println(rand.float64()) - // Using local random number generator - my_rand := rand.create(1) - fmt.println(rand.float64(&my_rand)) } Possible Output: @@ -552,14 +409,11 @@ Possible Output: 0.511 */ -@(require_results) float64 :: proc(r: ^Rand = nil) -> (val: f64) { return f64(int63_max(1<<53, r)) / (1 << 53) } +@(require_results) float64 :: proc(gen := context.random_generator) -> (val: f64) { return f64(int63_max(1<<53, gen)) / (1 << 53) } /* Generates a random single floating point value in the range `[0, 1)` using the provided random number generator. If no generator is provided the global random number generator will be used. -Inputs: -- r: The random number generator to use, or nil for the global generator - Returns: - val: A random single floating point value in the range `[0, 1)` @@ -568,11 +422,7 @@ Example: import "core:fmt" float32_example :: proc() { - // Using the global random number generator fmt.println(rand.float32()) - // Using local random number generator - my_rand := rand.create(1) - fmt.println(rand.float32(&my_rand)) } Possible Output: @@ -581,7 +431,7 @@ Possible Output: 0.511 */ -@(require_results) float32 :: proc(r: ^Rand = nil) -> (val: f32) { return f32(int31_max(1<<24, r)) / (1 << 24) } +@(require_results) float32 :: proc(gen := context.random_generator) -> (val: f32) { return f32(int31_max(1<<24, gen)) / (1 << 24) } /* Generates a random double floating point value in the range `[low, high)` using the provided random number generator. If no generator is provided the global random number generator will be used. @@ -591,7 +441,6 @@ WARNING: Panics if `high < low` Inputs: - low: The lower bounds of the value, this value is inclusive - high: The upper bounds of the value, this value is exclusive -- r: The random number generator to use, or nil for the global generator Returns: - val: A random double floating point value in the range [low, high) @@ -601,11 +450,7 @@ Example: import "core:fmt" float64_range_example :: proc() { - // Using the global random number generator fmt.println(rand.float64_range(-10, 300)) - // Using local random number generator - my_rand := rand.create(1) - fmt.println(rand.float64_range(600, 900, &my_rand)) } Possible Output: @@ -614,9 +459,9 @@ Possible Output: 673.130 */ -@(require_results) float64_range :: proc(low, high: f64, r: ^Rand = nil) -> (val: f64) { +@(require_results) float64_range :: proc(low, high: f64, gen := context.random_generator) -> (val: f64) { assert(low <= high, "low must be lower than or equal to high") - val = (high-low)*float64(r) + low + val = (high-low)*float64(gen) + low if val >= high { val = max(low, high * (1 - math.F64_EPSILON)) } @@ -629,7 +474,6 @@ Generates a random single floating point value in the range `[low, high)` using Inputs: - low: The lower bounds of the value, this value is inclusive - high: The upper bounds of the value, this value is exclusive -- r: The random number generator to use, or nil for the global generator Returns: - val: A random single floating point value in the range [low, high) @@ -641,11 +485,7 @@ Example: import "core:fmt" float32_range_example :: proc() { - // Using the global random number generator fmt.println(rand.float32_range(-10, 300)) - // Using local random number generator - my_rand := rand.create(1) - fmt.println(rand.float32_range(600, 900, &my_rand)) } Possible Output: @@ -654,9 +494,9 @@ Possible Output: 673.130 */ -@(require_results) float32_range :: proc(low, high: f32, r: ^Rand = nil) -> (val: f32) { +@(require_results) float32_range :: proc(low, high: f32, gen := context.random_generator) -> (val: f32) { assert(low <= high, "low must be lower than or equal to high") - val = (high-low)*float32(r) + low + val = (high-low)*float32(gen) + low if val >= high { val = max(low, high * (1 - math.F32_EPSILON)) } @@ -669,7 +509,6 @@ Due to floating point precision there is no guarantee if the upper and lower bou Inputs: - p: The byte slice to fill -- r: The random number generator to use, or nil for the global generator Returns: - n: The number of bytes generated @@ -679,7 +518,6 @@ Example: import "core:fmt" read_example :: proc() { - // Using the global random number generator data: [8]byte n := rand.read(data[:]) fmt.println(n) @@ -693,12 +531,12 @@ Possible Output: */ @(require_results) -read :: proc(p: []byte, r: ^Rand = nil) -> (n: int) { +read :: proc(p: []byte, gen := context.random_generator) -> (n: int) { pos := i8(0) val := i64(0) for n = 0; n < len(p); n += 1 { if pos == 0 { - val = int63(r) + val = int63(gen) pos = 7 } p[n] = byte(val) @@ -715,7 +553,6 @@ Creates a slice of `int` filled with random values using the provided random num Inputs: - n: The size of the created slice -- r: The random number generator to use, or nil for the global generator - allocator: (default: context.allocator) Returns: @@ -728,16 +565,10 @@ Example: import "core:fmt" perm_example :: proc() -> (err: mem.Allocator_Error) { - // Using the global random number generator and using the context allocator data := rand.perm(4) or_return fmt.println(data) defer delete(data, context.allocator) - // Using local random number generator and temp allocator - my_rand := rand.create(1) - data_tmp := rand.perm(4, &my_rand, context.temp_allocator) or_return - fmt.println(data_tmp) - return } @@ -748,10 +579,10 @@ Possible Output: */ @(require_results) -perm :: proc(n: int, r: ^Rand = nil, allocator := context.allocator) -> (res: []int, err: mem.Allocator_Error) #optional_allocator_error { +perm :: proc(n: int, allocator := context.allocator, gen := context.random_generator) -> (res: []int, err: mem.Allocator_Error) #optional_allocator_error { m := make([]int, n, allocator) or_return for i := 0; i < n; i += 1 { - j := int_max(i+1, r) + j := int_max(i+1, gen) m[i] = m[j] m[j] = i } @@ -763,14 +594,12 @@ Randomizes the ordering of elements for the provided slice. If no generator is p Inputs: - array: The slice to randomize -- r: The random number generator to use, or nil for the global generator Example: import "core:math/rand" import "core:fmt" shuffle_example :: proc() { - // Using the global random number generator data: [4]int = { 1, 2, 3, 4 } fmt.println(data) // the contents are in order rand.shuffle(data[:]) @@ -783,14 +612,20 @@ Possible Output: [2, 4, 3, 1] */ -shuffle :: proc(array: $T/[]$E, r: ^Rand = nil) { +shuffle :: proc(array: $T/[]$E, gen := context.random_generator) { n := i64(len(array)) if n < 2 { return } - for i := i64(n - 1); i > 0; i -= 1 { - j := int63_max(i + 1, r) + i := n - 1 + for ; i > (1<<31 - 2); i -= 1 { + j := int63_max(i + 1, gen) + array[i], array[j] = array[j], array[i] + } + + for ; i > 0; i -= 1 { + j := int31_max(i32(i + 1), gen) array[i], array[j] = array[j], array[i] } } @@ -800,7 +635,6 @@ Returns a random element from the provided slice. If no generator is provided th Inputs: - array: The slice to choose an element from -- r: The random number generator to use, or nil for the global generator Returns: - res: A random element from `array` @@ -810,7 +644,6 @@ Example: import "core:fmt" choice_example :: proc() { - // Using the global random number generator data: [4]int = { 1, 2, 3, 4 } fmt.println(rand.choice(data[:])) fmt.println(rand.choice(data[:])) @@ -827,29 +660,29 @@ Possible Output: */ @(require_results) -choice :: proc(array: $T/[]$E, r: ^Rand = nil) -> (res: E) { +choice :: proc(array: $T/[]$E, gen := context.random_generator) -> (res: E) { n := i64(len(array)) if n < 1 { return E{} } - return array[int63_max(n, r)] + return array[int63_max(n, gen)] } @(require_results) -choice_enum :: proc($T: typeid, r: ^Rand = nil) -> T +choice_enum :: proc($T: typeid, gen := context.random_generator) -> T where intrinsics.type_is_enum(T), size_of(T) <= 8, - len(T) == cap(T) /* Only allow contiguous enum types */ + len(T) == cap(T) /* Only allow contiguous enum types */ \ { when intrinsics.type_is_unsigned(intrinsics.type_core_type(T)) && u64(max(T)) > u64(max(i64)) { - i := uint64(r) % u64(len(T)) + i := uint64(gen) % u64(len(T)) i += u64(min(T)) return T(i) } else { - i := int63_max(i64(len(T)), r) + i := int63_max(i64(len(T)), gen) i += i64(min(T)) return T(i) } diff --git a/core/math/rand/system_darwin.odin b/core/math/rand/system_darwin.odin deleted file mode 100644 index 756f7fcae..000000000 --- a/core/math/rand/system_darwin.odin +++ /dev/null @@ -1,22 +0,0 @@ -package rand - -import "core:sys/darwin" - -@(require_results) -_system_random :: proc() -> u64 { - for { - value: u64 - ret := darwin.syscall_getentropy(([^]u8)(&value), size_of(value)) - if ret < 0 { - switch ret { - case -4: // EINTR - continue - case -78: // ENOSYS - panic("getentropy not available in kernel") - case: - panic("getentropy failed") - } - } - return value - } -} \ No newline at end of file diff --git a/core/math/rand/system_js.odin b/core/math/rand/system_js.odin deleted file mode 100644 index b9b71c4a6..000000000 --- a/core/math/rand/system_js.odin +++ /dev/null @@ -1,14 +0,0 @@ -package rand - -foreign import "odin_env" -foreign odin_env { - @(link_name = "rand_bytes") - env_rand_bytes :: proc "contextless" (buf: []byte) --- -} - -@(require_results) -_system_random :: proc() -> u64 { - buf: [8]u8 - env_rand_bytes(buf[:]) - return transmute(u64)buf -} diff --git a/core/math/rand/system_linux.odin b/core/math/rand/system_linux.odin deleted file mode 100644 index 42c9f86fa..000000000 --- a/core/math/rand/system_linux.odin +++ /dev/null @@ -1,29 +0,0 @@ -package rand - -import "core:sys/linux" - -@(require_results) -_system_random :: proc() -> u64 { - for { - value: u64 - value_buf := (cast([^]u8)&value)[:size_of(u64)] - _, errno := linux.getrandom(value_buf, {}) - #partial switch errno { - case .NONE: - // Do nothing - case .EINTR: - // Call interupted by a signal handler, just retry the request. - continue - case .ENOSYS: - // The kernel is apparently prehistoric (< 3.17 circa 2014) - // and does not support getrandom. - panic("getrandom not available in kernel") - case: - // All other failures are things that should NEVER happen - // unless the kernel interface changes (ie: the Linux - // developers break userland). - panic("getrandom failed") - } - return value - } -} \ No newline at end of file diff --git a/core/math/rand/system_windows.odin b/core/math/rand/system_windows.odin deleted file mode 100644 index c6d68816d..000000000 --- a/core/math/rand/system_windows.odin +++ /dev/null @@ -1,13 +0,0 @@ -package rand - -import win32 "core:sys/windows" - -@(require_results) -_system_random :: proc() -> u64 { - value: u64 - status := win32.BCryptGenRandom(nil, ([^]u8)(&value), size_of(value), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG) - if status < 0 { - panic("BCryptGenRandom failed") - } - return value -} \ No newline at end of file diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index eba79eacf..a5b93ad05 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -748,9 +748,7 @@ dynamic_pool_alloc_bytes :: proc(p: ^Dynamic_Pool, bytes: int) -> ([]byte, Alloc return } - n := bytes - extra := p.alignment - (n % p.alignment) - n += extra + n := align_formula(bytes, p.alignment) if n > p.block_size { return nil, .Invalid_Argument } @@ -1124,7 +1122,7 @@ buddy_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, case .Query_Info: info := (^Allocator_Query_Info)(old_memory) if info != nil && info.pointer != nil { - ptr := old_memory + ptr := info.pointer if !(b.head <= ptr && ptr <= b.tail) { return nil, .Invalid_Pointer } diff --git a/core/mem/raw.odin b/core/mem/raw.odin index 56790e959..f56206957 100644 --- a/core/mem/raw.odin +++ b/core/mem/raw.odin @@ -11,12 +11,15 @@ Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array Raw_Map :: runtime.Raw_Map Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer -Raw_Complex64 :: struct {real, imag: f32} -Raw_Complex128 :: struct {real, imag: f64} -Raw_Quaternion128 :: struct {imag, jmag, kmag: f32, real: f32} -Raw_Quaternion256 :: struct {imag, jmag, kmag: f64, real: f64} -Raw_Quaternion128_Vector_Scalar :: struct {vector: [3]f32, scalar: f32} -Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64} +Raw_Complex32 :: runtime.Raw_Complex32 +Raw_Complex64 :: runtime.Raw_Complex64 +Raw_Complex128 :: runtime.Raw_Complex128 +Raw_Quaternion64 :: runtime.Raw_Quaternion64 +Raw_Quaternion128 :: runtime.Raw_Quaternion128 +Raw_Quaternion256 :: runtime.Raw_Quaternion256 +Raw_Quaternion64_Vector_Scalar :: runtime.Raw_Quaternion64_Vector_Scalar +Raw_Quaternion128_Vector_Scalar :: runtime.Raw_Quaternion128_Vector_Scalar +Raw_Quaternion256_Vector_Scalar :: runtime.Raw_Quaternion256_Vector_Scalar make_any :: proc "contextless" (data: rawptr, id: typeid) -> any { return transmute(any)Raw_Any{data, id} diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin new file mode 100644 index 000000000..f5e428d87 --- /dev/null +++ b/core/mem/rollback_stack_allocator.odin @@ -0,0 +1,341 @@ +package mem + +// The Rollback Stack Allocator was designed for the test runner to be fast, +// able to grow, and respect the Tracking Allocator's requirement for +// individual frees. It is not overly concerned with fragmentation, however. +// +// It has support for expansion when configured with a block allocator and +// limited support for out-of-order frees. +// +// Allocation has constant-time best and usual case performance. +// At worst, it is linear according to the number of memory blocks. +// +// Allocation follows a first-fit strategy when there are multiple memory +// blocks. +// +// Freeing has constant-time best and usual case performance. +// At worst, it is linear according to the number of memory blocks and number +// of freed items preceding the last item in a block. +// +// Resizing has constant-time performance, if it's the last item in a block, or +// the new size is smaller. Naturally, this becomes linear-time if there are +// multiple blocks to search for the pointer's owning block. Otherwise, the +// allocator defaults to a combined alloc & free operation internally. +// +// Out-of-order freeing is accomplished by collapsing a run of freed items +// from the last allocation backwards. +// +// Each allocation has an overhead of 8 bytes and any extra bytes to satisfy +// the requested alignment. + +import "base:runtime" + +ROLLBACK_STACK_DEFAULT_BLOCK_SIZE :: 4 * Megabyte + +// This limitation is due to the size of `prev_ptr`, but it is only for the +// head block; any allocation in excess of the allocator's `block_size` is +// valid, so long as the block allocator can handle it. +// +// This is because allocations over the block size are not split up if the item +// within is freed; they are immediately returned to the block allocator. +ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE :: 2 * Gigabyte + + +Rollback_Stack_Header :: bit_field u64 { + prev_offset: uintptr | 32, + is_free: bool | 1, + prev_ptr: uintptr | 31, +} + +Rollback_Stack_Block :: struct { + next_block: ^Rollback_Stack_Block, + last_alloc: rawptr, + offset: uintptr, + buffer: []byte, +} + +Rollback_Stack :: struct { + head: ^Rollback_Stack_Block, + block_size: int, + block_allocator: Allocator, +} + + +@(private="file", require_results) +rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { + start := raw_data(block.buffer) + end := start[block.offset:] + return start < ptr && ptr <= end +} + +@(private="file", require_results) +rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( + parent: ^Rollback_Stack_Block, + block: ^Rollback_Stack_Block, + header: ^Rollback_Stack_Header, + err: Allocator_Error, +) { + for block = stack.head; block != nil; block = block.next_block { + if rb_ptr_in_bounds(block, ptr) { + header = cast(^Rollback_Stack_Header)(cast(uintptr)ptr - size_of(Rollback_Stack_Header)) + return + } + parent = block + } + return nil, nil, nil, .Invalid_Pointer +} + +@(private="file", require_results) +rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( + block: ^Rollback_Stack_Block, + header: ^Rollback_Stack_Header, + ok: bool, +) { + for block = stack.head; block != nil; block = block.next_block { + if block.last_alloc == ptr { + header = cast(^Rollback_Stack_Header)(cast(uintptr)ptr - size_of(Rollback_Stack_Header)) + return block, header, true + } + } + return nil, nil, false +} + +@(private="file") +rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) { + header := header + for block.offset > 0 && header.is_free { + block.offset = header.prev_offset + block.last_alloc = raw_data(block.buffer)[header.prev_ptr:] + header = cast(^Rollback_Stack_Header)(raw_data(block.buffer)[header.prev_ptr - size_of(Rollback_Stack_Header):]) + } +} + +@(private="file", require_results) +rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { + parent, block, header := rb_find_ptr(stack, ptr) or_return + if header.is_free { + return .Invalid_Pointer + } + header.is_free = true + if block.last_alloc == ptr { + block.offset = header.prev_offset + rb_rollback_block(block, header) + } + if parent != nil && block.offset == 0 { + parent.next_block = block.next_block + runtime.mem_free_with_size(block, size_of(Rollback_Stack_Block) + len(block.buffer), stack.block_allocator) + } + return nil +} + +@(private="file") +rb_free_all :: proc(stack: ^Rollback_Stack) { + for block := stack.head.next_block; block != nil; /**/ { + next_block := block.next_block + runtime.mem_free_with_size(block, size_of(Rollback_Stack_Block) + len(block.buffer), stack.block_allocator) + block = next_block + } + + stack.head.next_block = nil + stack.head.last_alloc = nil + stack.head.offset = 0 +} + +@(private="file", require_results) +rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) { + if ptr != nil { + if block, _, ok := rb_find_last_alloc(stack, ptr); ok { + // `block.offset` should never underflow because it is contingent + // on `old_size` in the first place, assuming sane arguments. + assert(block.offset >= cast(uintptr)old_size, "Rollback Stack Allocator received invalid `old_size`.") + + if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) { + // Prevent singleton allocations from fragmenting by forbidding + // them to shrink, removing the possibility of overflow bugs. + if len(block.buffer) <= stack.block_size { + block.offset += cast(uintptr)size - cast(uintptr)old_size + } + #no_bounds_check return (cast([^]byte)ptr)[:size], nil + } + } + } + + result = rb_alloc(stack, size, alignment) or_return + runtime.mem_copy_non_overlapping(raw_data(result), ptr, old_size) + err = rb_free(stack, ptr) + + return +} + +@(private="file", require_results) +rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byte, err: Allocator_Error) { + parent: ^Rollback_Stack_Block + for block := stack.head; /**/; block = block.next_block { + when !ODIN_DISABLE_ASSERT { + allocated_new_block: bool + } + + if block == nil { + if stack.block_allocator.procedure == nil { + return nil, .Out_Of_Memory + } + + minimum_size_required := size_of(Rollback_Stack_Header) + size + alignment - 1 + new_block_size := max(minimum_size_required, stack.block_size) + block = rb_make_block(new_block_size, stack.block_allocator) or_return + parent.next_block = block + when !ODIN_DISABLE_ASSERT { + allocated_new_block = true + } + } + + start := raw_data(block.buffer)[block.offset:] + padding := cast(uintptr)calc_padding_with_header(cast(uintptr)start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) + + if block.offset + padding + cast(uintptr)size > cast(uintptr)len(block.buffer) { + when !ODIN_DISABLE_ASSERT { + if allocated_new_block { + panic("Rollback Stack Allocator allocated a new block but did not use it.") + } + } + parent = block + continue + } + + header := cast(^Rollback_Stack_Header)(start[padding - size_of(Rollback_Stack_Header):]) + ptr := start[padding:] + + header^ = { + prev_offset = block.offset, + prev_ptr = uintptr(0) if block.last_alloc == nil else cast(uintptr)block.last_alloc - cast(uintptr)raw_data(block.buffer), + is_free = false, + } + + block.last_alloc = ptr + block.offset += padding + cast(uintptr)size + + if len(block.buffer) > stack.block_size { + // This block exceeds the allocator's standard block size and is considered a singleton. + // Prevent any further allocations on it. + block.offset = cast(uintptr)len(block.buffer) + } + + #no_bounds_check return ptr[:size], nil + } + + return nil, .Out_Of_Memory +} + +@(private="file", require_results) +rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) { + buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return + + block = cast(^Rollback_Stack_Block)raw_data(buffer) + #no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):] + return +} + + +rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) { + MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr) + assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location) + + block := cast(^Rollback_Stack_Block)raw_data(buffer) + block^ = {} + #no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):] + + stack^ = {} + stack.head = block + stack.block_size = len(block.buffer) +} + +rollback_stack_init_dynamic :: proc( + stack: ^Rollback_Stack, + block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, + block_allocator := context.allocator, + location := #caller_location, +) -> Allocator_Error { + assert(block_size >= size_of(Rollback_Stack_Header) + size_of(rawptr), "Rollback Stack Allocator block size is too small.", location) + when size_of(int) > 4 { + // It's impossible to specify an argument in excess when your integer + // size is insufficient; check only on platforms with big enough ints. + assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.", location) + } + + block := rb_make_block(block_size, block_allocator) or_return + + stack^ = {} + stack.head = block + stack.block_size = block_size + stack.block_allocator = block_allocator + + return nil +} + +rollback_stack_init :: proc { + rollback_stack_init_buffered, + rollback_stack_init_dynamic, +} + +rollback_stack_destroy :: proc(stack: ^Rollback_Stack) { + if stack.block_allocator.procedure != nil { + rb_free_all(stack) + free(stack.head, stack.block_allocator) + } + stack^ = {} +} + +@(require_results) +rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator { + return Allocator { + data = stack, + procedure = rollback_stack_allocator_proc, + } +} + +@(require_results) +rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, location := #caller_location, +) -> (result: []byte, err: Allocator_Error) { + stack := cast(^Rollback_Stack)allocator_data + + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + assert(size >= 0, "Size must be positive or zero.", location) + assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location) + result = rb_alloc(stack, size, alignment) or_return + + if mode == .Alloc { + zero_slice(result) + } + + case .Free: + err = rb_free(stack, old_memory) + + case .Free_All: + rb_free_all(stack) + + case .Resize, .Resize_Non_Zeroed: + assert(size >= 0, "Size must be positive or zero.", location) + assert(old_size >= 0, "Old size must be positive or zero.", location) + assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location) + result = rb_resize(stack, old_memory, old_size, size, alignment) or_return + + #no_bounds_check if mode == .Resize && size > old_size { + zero_slice(result[old_size:]) + } + + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed} + } + return nil, nil + + case .Query_Info: + return nil, .Mode_Not_Implemented + } + + return +} diff --git a/core/mem/tlsf/LICENSE b/core/mem/tlsf/LICENSE new file mode 100644 index 000000000..9d668ce02 --- /dev/null +++ b/core/mem/tlsf/LICENSE @@ -0,0 +1,36 @@ +Original BSD-3 license: + +Two Level Segregated Fit memory allocator, version 3.1. +Written by Matthew Conte + http://tlsf.baisoku.org + +Based on the original documentation by Miguel Masmano: + http://www.gii.upv.es/tlsf/main/docs + +This implementation was written to the specification +of the document, therefore no GPL restrictions apply. + +Copyright (c) 2006-2016, Matthew Conte +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/core/mem/tlsf/tlsf.odin b/core/mem/tlsf/tlsf.odin new file mode 100644 index 000000000..8ec5f52b7 --- /dev/null +++ b/core/mem/tlsf/tlsf.odin @@ -0,0 +1,161 @@ +/* + Copyright 2024 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Matt Conte: Original C implementation, see LICENSE file in this package + Jeroen van Rijn: Source port +*/ + +// package mem_tlsf implements a Two Level Segregated Fit memory allocator. +package mem_tlsf + +import "base:runtime" + +Error :: enum byte { + None = 0, + Invalid_Backing_Allocator = 1, + Invalid_Alignment = 2, + Backing_Buffer_Too_Small = 3, + Backing_Buffer_Too_Large = 4, + Backing_Allocator_Error = 5, +} + + +Allocator :: struct { + // Empty lists point at this block to indicate they are free. + block_null: Block_Header, + + // Bitmaps for free lists. + fl_bitmap: u32 `fmt:"-"`, + sl_bitmap: [FL_INDEX_COUNT]u32 `fmt:"-"`, + + // Head of free lists. + blocks: [FL_INDEX_COUNT][SL_INDEX_COUNT]^Block_Header `fmt:"-"`, + + // Keep track of pools so we can deallocate them. + // If `pool.allocator` is blank, we don't do anything. + // We also use this linked list of pools to report + // statistics like how much memory is still available, + // fragmentation, etc. + pool: Pool, +} +#assert(size_of(Allocator) % ALIGN_SIZE == 0) + + + + +@(require_results) +allocator :: proc(t: ^Allocator) -> runtime.Allocator { + return runtime.Allocator{ + procedure = allocator_proc, + data = t, + } +} + +@(require_results) +init_from_buffer :: proc(control: ^Allocator, buf: []byte) -> Error { + assert(control != nil) + if uintptr(raw_data(buf)) % ALIGN_SIZE != 0 { + return .Invalid_Alignment + } + + pool_bytes := align_down(len(buf) - POOL_OVERHEAD, ALIGN_SIZE) + if pool_bytes < BLOCK_SIZE_MIN { + return .Backing_Buffer_Too_Small + } else if pool_bytes > BLOCK_SIZE_MAX { + return .Backing_Buffer_Too_Large + } + + clear(control) + return pool_add(control, buf[:]) +} + +@(require_results) +init_from_allocator :: proc(control: ^Allocator, backing: runtime.Allocator, initial_pool_size: int, new_pool_size := 0) -> Error { + assert(control != nil) + pool_bytes := align_up(uint(initial_pool_size) + POOL_OVERHEAD, ALIGN_SIZE) + if pool_bytes < BLOCK_SIZE_MIN { + return .Backing_Buffer_Too_Small + } else if pool_bytes > BLOCK_SIZE_MAX { + return .Backing_Buffer_Too_Large + } + + buf, backing_err := runtime.make_aligned([]byte, pool_bytes, ALIGN_SIZE, backing) + if backing_err != nil { + return .Backing_Allocator_Error + } + err := init_from_buffer(control, buf) + control.pool = Pool{ + data = buf, + allocator = backing, + } + return err +} +init :: proc{init_from_buffer, init_from_allocator} + +destroy :: proc(control: ^Allocator) { + if control == nil { return } + + if control.pool.allocator.procedure != nil { + runtime.delete(control.pool.data, control.pool.allocator) + } + + // No need to call `pool_remove` or anything, as they're they're embedded in the backing memory. + // We do however need to free the `Pool` tracking entities and the backing memory itself. + // As `Allocator` is embedded in the first backing slice, the `control` pointer will be + // invalid after this call. + for p := control.pool.next; p != nil; { + next := p.next + + // Free the allocation on the backing allocator + runtime.delete(p.data, p.allocator) + free(p, p.allocator) + + p = next + } +} + +allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, runtime.Allocator_Error) { + + control := (^Allocator)(allocator_data) + if control == nil { + return nil, .Invalid_Argument + } + + switch mode { + case .Alloc: + return alloc_bytes(control, uint(size), uint(alignment)) + case .Alloc_Non_Zeroed: + return alloc_bytes_non_zeroed(control, uint(size), uint(alignment)) + + case .Free: + free_with_size(control, old_memory, uint(old_size)) + return nil, nil + + case .Free_All: + // NOTE: this doesn't work right at the moment, Jeroen has it on his to-do list :) + // clear(control) + return nil, .Mode_Not_Implemented + + case .Resize: + return resize(control, old_memory, uint(old_size), uint(size), uint(alignment)) + + case .Resize_Non_Zeroed: + return resize_non_zeroed(control, old_memory, uint(old_size), uint(size), uint(alignment)) + + case .Query_Features: + set := (^runtime.Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, /* .Free_All, */ .Resize, .Resize_Non_Zeroed, .Query_Features} + } + return nil, nil + + case .Query_Info: + return nil, .Mode_Not_Implemented + } + + return nil, nil +} diff --git a/core/mem/tlsf/tlsf_internal.odin b/core/mem/tlsf/tlsf_internal.odin new file mode 100644 index 000000000..cac151183 --- /dev/null +++ b/core/mem/tlsf/tlsf_internal.odin @@ -0,0 +1,738 @@ +/* + Copyright 2024 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Matt Conte: Original C implementation, see LICENSE file in this package + Jeroen van Rijn: Source port +*/ + + +package mem_tlsf + +import "base:intrinsics" +import "base:runtime" +// import "core:fmt" + +// log2 of number of linear subdivisions of block sizes. +// Larger values require more memory in the control structure. +// Values of 4 or 5 are typical. +TLSF_SL_INDEX_COUNT_LOG2 :: #config(TLSF_SL_INDEX_COUNT_LOG2, 5) + +// All allocation sizes and addresses are aligned to 4/8 bytes +ALIGN_SIZE_LOG2 :: 3 when size_of(uintptr) == 8 else 2 + +// We can increase this to support larger allocation sizes, +// at the expense of more overhead in the TLSF structure +FL_INDEX_MAX :: 32 when size_of(uintptr) == 8 else 30 +#assert(FL_INDEX_MAX < 36) + +ALIGN_SIZE :: 1 << ALIGN_SIZE_LOG2 +SL_INDEX_COUNT :: 1 << TLSF_SL_INDEX_COUNT_LOG2 +FL_INDEX_SHIFT :: TLSF_SL_INDEX_COUNT_LOG2 + ALIGN_SIZE_LOG2 +FL_INDEX_COUNT :: FL_INDEX_MAX - FL_INDEX_SHIFT + 1 +SMALL_BLOCK_SIZE :: 1 << FL_INDEX_SHIFT + +/* +We support allocations of sizes up to (1 << `FL_INDEX_MAX`) bits. +However, because we linearly subdivide the second-level lists, and +our minimum size granularity is 4 bytes, it doesn't make sense to +create first-level lists for sizes smaller than `SL_INDEX_COUNT` * 4, +or (1 << (`TLSF_SL_INDEX_COUNT_LOG2` + 2)) bytes, as there we will be +trying to split size ranges into more slots than we have available. +Instead, we calculate the minimum threshold size, and place all +blocks below that size into the 0th first-level list. +*/ + +// SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage tree +#assert(size_of(uint) * 8 >= SL_INDEX_COUNT) + +// Ensure we've properly tuned our sizes. +#assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT) + +#assert(size_of(Allocator) % ALIGN_SIZE == 0) + +Pool :: struct { + data: []u8 `fmt:"-"`, + allocator: runtime.Allocator, + next: ^Pool, +} + + +/* +Block header structure. + +There are several implementation subtleties involved: +- The `prev_phys_block` field is only valid if the previous block is free. +- The `prev_phys_block` field is actually stored at the end of the + previous block. It appears at the beginning of this structure only to + simplify the implementation. +- The `next_free` / `prev_free` fields are only valid if the block is free. +*/ +Block_Header :: struct { + prev_phys_block: ^Block_Header, + size: uint, // The size of this block, excluding the block header + + // Next and previous free blocks. + next_free: ^Block_Header, + prev_free: ^Block_Header, +} +#assert(offset_of(Block_Header, prev_phys_block) == 0) + +/* +Since block sizes are always at least a multiple of 4, the two least +significant bits of the size field are used to store the block status: +- bit 0: whether block is busy or free +- bit 1: whether previous block is busy or free +*/ +BLOCK_HEADER_FREE :: uint(1 << 0) +BLOCK_HEADER_PREV_FREE :: uint(1 << 1) + +/* +The size of the block header exposed to used blocks is the `size` field. +The `prev_phys_block` field is stored *inside* the previous free block. +*/ +BLOCK_HEADER_OVERHEAD :: uint(size_of(uint)) + +POOL_OVERHEAD :: 2 * BLOCK_HEADER_OVERHEAD + +// User data starts directly after the size field in a used block. +BLOCK_START_OFFSET :: offset_of(Block_Header, size) + size_of(Block_Header{}.size) + +/* +A free block must be large enough to store its header minus the size of +the `prev_phys_block` field, and no larger than the number of addressable +bits for `FL_INDEX`. +*/ +BLOCK_SIZE_MIN :: uint(size_of(Block_Header) - size_of(^Block_Header)) +BLOCK_SIZE_MAX :: uint(1) << FL_INDEX_MAX + +/* + TLSF achieves O(1) cost for `alloc` and `free` operations by limiting + the search for a free block to a free list of guaranteed size + adequate to fulfill the request, combined with efficient free list + queries using bitmasks and architecture-specific bit-manipulation + routines. + + NOTE: TLSF spec relies on ffs/fls returning value 0..31. +*/ + +@(require_results) +ffs :: proc "contextless" (word: u32) -> (bit: i32) { + return -1 if word == 0 else i32(intrinsics.count_trailing_zeros(word)) +} + +@(require_results) +fls :: proc "contextless" (word: u32) -> (bit: i32) { + N :: (size_of(u32) * 8) - 1 + return i32(N - intrinsics.count_leading_zeros(word)) +} + +@(require_results) +fls_uint :: proc "contextless" (size: uint) -> (bit: i32) { + N :: (size_of(uint) * 8) - 1 + return i32(N - intrinsics.count_leading_zeros(size)) +} + +@(require_results) +block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) { + return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE) +} + +block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) { + old_size := block.size + block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)) +} + +@(require_results) +block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) { + return block_size(block) == 0 +} + +@(require_results) +block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) { + return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE +} + +block_set_free :: proc "contextless" (block: ^Block_Header) { + block.size |= BLOCK_HEADER_FREE +} + +block_set_used :: proc "contextless" (block: ^Block_Header) { + block.size &~= BLOCK_HEADER_FREE +} + +@(require_results) +block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) { + return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE +} + +block_set_prev_free :: proc "contextless" (block: ^Block_Header) { + block.size |= BLOCK_HEADER_PREV_FREE +} + +block_set_prev_used :: proc "contextless" (block: ^Block_Header) { + block.size &~= BLOCK_HEADER_PREV_FREE +} + +@(require_results) +block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) { + return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET) +} + +@(require_results) +block_to_ptr :: proc(block: ^Block_Header) -> (ptr: rawptr) { + return rawptr(uintptr(block) + BLOCK_START_OFFSET) +} + +// Return location of next block after block of given size. +@(require_results) +offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) { + return (^Block_Header)(uintptr(ptr) + uintptr(size)) +} + +@(require_results) +offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) { + return (^Block_Header)(uintptr(ptr) - uintptr(size)) +} + +// Return location of previous block. +@(require_results) +block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) { + assert(block_is_prev_free(block), "previous block must be free") + return block.prev_phys_block +} + +// Return location of next existing block. +@(require_results) +block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) { + return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD) +} + +// Link a new block with its physical neighbor, return the neighbor. +@(require_results) +block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) { + next = block_next(block) + next.prev_phys_block = block + return +} + +block_mark_as_free :: proc(block: ^Block_Header) { + // Link the block to the next block, first. + next := block_link_next(block) + block_set_prev_free(next) + block_set_free(block) +} + +block_mark_as_used :: proc(block: ^Block_Header) { + next := block_next(block) + block_set_prev_used(next) + block_set_used(block) +} + +@(require_results) +align_up :: proc(x, align: uint) -> (aligned: uint) { + assert(0 == (align & (align - 1)), "must align to a power of two") + return (x + (align - 1)) &~ (align - 1) +} + +@(require_results) +align_down :: proc(x, align: uint) -> (aligned: uint) { + assert(0 == (align & (align - 1)), "must align to a power of two") + return x - (x & (align - 1)) +} + +@(require_results) +align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) { + assert(0 == (align & (align - 1)), "must align to a power of two") + align_mask := uintptr(align) - 1 + _ptr := uintptr(ptr) + _aligned := (_ptr + align_mask) &~ (align_mask) + return rawptr(_aligned) +} + +// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. +@(require_results) +adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) { + if size == 0 { + return 0 + } + + // aligned size must not exceed `BLOCK_SIZE_MAX`, or we'll go out of bounds on `sl_bitmap`. + if aligned := align_up(size, align); aligned < BLOCK_SIZE_MAX { + adjusted = min(aligned, BLOCK_SIZE_MAX) + } + return +} + +// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. +@(require_results) +adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) { + if size == 0 { + return 0, nil + } + + // aligned size must not exceed `BLOCK_SIZE_MAX`, or we'll go out of bounds on `sl_bitmap`. + if aligned := align_up(size, align); aligned < BLOCK_SIZE_MAX { + adjusted = min(aligned, BLOCK_SIZE_MAX) + } else { + err = .Out_Of_Memory + } + return +} + +// TLSF utility functions. In most cases these are direct translations of +// the documentation in the research paper. + +@(optimization_mode="favor_size", require_results) +mapping_insert :: proc(size: uint) -> (fl, sl: i32) { + if size < SMALL_BLOCK_SIZE { + // Store small blocks in first list. + sl = i32(size) / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT) + } else { + fl = fls_uint(size) + sl = i32(size >> (uint(fl) - TLSF_SL_INDEX_COUNT_LOG2)) ~ (1 << TLSF_SL_INDEX_COUNT_LOG2) + fl -= (FL_INDEX_SHIFT - 1) + } + return +} + +@(optimization_mode="favor_size", require_results) +mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) { + rounded = size + if size >= SMALL_BLOCK_SIZE { + round := uint(1 << (uint(fls_uint(size) - TLSF_SL_INDEX_COUNT_LOG2))) - 1 + rounded += round + } + return +} + +// This version rounds up to the next block size (for allocations) +@(optimization_mode="favor_size", require_results) +mapping_search :: proc(size: uint) -> (fl, sl: i32) { + return mapping_insert(mapping_round(size)) +} + +@(require_results) +search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) { + // First, search for a block in the list associated with the given fl/sl index. + fl := fli^; sl := sli^ + + sl_map := control.sl_bitmap[fli^] & (~u32(0) << uint(sl)) + if sl_map == 0 { + // No block exists. Search in the next largest first-level list. + fl_map := control.fl_bitmap & (~u32(0) << uint(fl + 1)) + if fl_map == 0 { + // No free blocks available, memory has been exhausted. + return {} + } + + fl = ffs(fl_map) + fli^ = fl + sl_map = control.sl_bitmap[fl] + } + assert(sl_map != 0, "internal error - second level bitmap is null") + sl = ffs(sl_map) + sli^ = sl + + // Return the first block in the free list. + return control.blocks[fl][sl] +} + +// Remove a free block from the free list. +remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) { + prev := block.prev_free + next := block.next_free + assert(prev != nil, "prev_free can not be nil") + assert(next != nil, "next_free can not be nil") + next.prev_free = prev + prev.next_free = next + + // If this block is the head of the free list, set new head. + if control.blocks[fl][sl] == block { + control.blocks[fl][sl] = next + + // If the new head is nil, clear the bitmap + if next == &control.block_null { + control.sl_bitmap[fl] &~= (u32(1) << uint(sl)) + + // If the second bitmap is now empty, clear the fl bitmap + if control.sl_bitmap[fl] == 0 { + control.fl_bitmap &~= (u32(1) << uint(fl)) + } + } + } +} + +// Insert a free block into the free block list. +insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) { + current := control.blocks[fl][sl] + assert(current != nil, "free lists cannot have a nil entry") + assert(block != nil, "cannot insert a nil entry into the free list") + block.next_free = current + block.prev_free = &control.block_null + current.prev_free = block + + assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE), "block not properly aligned") + + // Insert the new block at the head of the list, and mark the first- and second-level bitmaps appropriately. + control.blocks[fl][sl] = block + control.fl_bitmap |= (u32(1) << uint(fl)) + control.sl_bitmap[fl] |= (u32(1) << uint(sl)) +} + +// Remove a given block from the free list. +block_remove :: proc(control: ^Allocator, block: ^Block_Header) { + fl, sl := mapping_insert(block_size(block)) + remove_free_block(control, block, fl, sl) +} + +// Insert a given block into the free list. +block_insert :: proc(control: ^Allocator, block: ^Block_Header) { + fl, sl := mapping_insert(block_size(block)) + insert_free_block(control, block, fl, sl) +} + +@(require_results) +block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) { + return block_size(block) >= size_of(Block_Header) + size +} + +// Split a block into two, the second of which is free. +@(require_results) +block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) { + // Calculate the amount of space left in the remaining block. + remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD) + + remain_size := block_size(block) - (size + BLOCK_HEADER_OVERHEAD) + + assert(block_to_ptr(remaining) == align_ptr(block_to_ptr(remaining), ALIGN_SIZE), + "remaining block not aligned properly") + + assert(block_size(block) == remain_size + size + BLOCK_HEADER_OVERHEAD) + block_set_size(remaining, remain_size) + assert(block_size(remaining) >= BLOCK_SIZE_MIN, "block split with invalid size") + + block_set_size(block, size) + block_mark_as_free(remaining) + + return remaining +} + +// Absorb a free block's storage into an adjacent previous free block. +@(require_results) +block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) { + assert(!block_is_last(prev), "previous block can't be last") + // Note: Leaves flags untouched. + prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD + _ = block_link_next(prev) + return prev +} + +// Merge a just-freed block with an adjacent previous free block. +@(require_results) +block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) { + merged = block + if (block_is_prev_free(block)) { + prev := block_prev(block) + assert(prev != nil, "prev physical block can't be nil") + assert(block_is_free(prev), "prev block is not free though marked as such") + block_remove(control, prev) + merged = block_absorb(prev, block) + } + return merged +} + +// Merge a just-freed block with an adjacent free block. +@(require_results) +block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) { + merged = block + next := block_next(block) + assert(next != nil, "next physical block can't be nil") + + if (block_is_free(next)) { + assert(!block_is_last(block), "previous block can't be last") + block_remove(control, next) + merged = block_absorb(block, next) + } + return merged +} + +// Trim any trailing block space off the end of a free block, return to pool. +block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { + assert(block_is_free(block), "block must be free") + if (block_can_split(block, size)) { + remaining_block := block_split(block, size) + _ = block_link_next(block) + block_set_prev_free(remaining_block) + block_insert(control, remaining_block) + } +} + +// Trim any trailing block space off the end of a used block, return to pool. +block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { + assert(!block_is_free(block), "Block must be used") + if (block_can_split(block, size)) { + // If the next block is free, we must coalesce. + remaining_block := block_split(block, size) + block_set_prev_used(remaining_block) + + remaining_block = block_merge_next(control, remaining_block) + block_insert(control, remaining_block) + } +} + +// Trim leading block space, return to pool. +@(require_results) +block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) { + remaining = block + if block_can_split(block, size) { + // We want the 2nd block. + remaining = block_split(block, size - BLOCK_HEADER_OVERHEAD) + block_set_prev_free(remaining) + + _ = block_link_next(block) + block_insert(control, block) + } + return remaining +} + +@(require_results) +block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) { + fl, sl: i32 + if size != 0 { + fl, sl = mapping_search(size) + + /* + `mapping_search` can futz with the size, so for excessively large sizes it can sometimes wind up + with indices that are off the end of the block array. So, we protect against that here, + since this is the only call site of `mapping_search`. Note that we don't need to check `sl`, + as it comes from a modulo operation that guarantees it's always in range. + */ + if fl < FL_INDEX_COUNT { + block = search_suitable_block(control, &fl, &sl) + } + } + + if block != nil { + assert(block_size(block) >= size) + remove_free_block(control, block, fl, sl) + } + return block +} + +@(require_results) +block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) { + if block != nil { + assert(size != 0, "Size must be non-zero") + block_trim_free(control, block, size) + block_mark_as_used(block) + res = ([^]byte)(block_to_ptr(block))[:size] + } + return +} + +// Clear control structure and point all empty lists at the null block +clear :: proc(control: ^Allocator) { + control.block_null.next_free = &control.block_null + control.block_null.prev_free = &control.block_null + + control.fl_bitmap = 0 + for i in 0.. (err: Error) { + assert(uintptr(raw_data(pool)) % ALIGN_SIZE == 0, "Added memory must be aligned") + + pool_overhead := POOL_OVERHEAD + pool_bytes := align_down(len(pool) - pool_overhead, ALIGN_SIZE) + + if pool_bytes < BLOCK_SIZE_MIN { + return .Backing_Buffer_Too_Small + } else if pool_bytes > BLOCK_SIZE_MAX { + return .Backing_Buffer_Too_Large + } + + // Create the main free block. Offset the start of the block slightly, + // so that the `prev_phys_block` field falls outside of the pool - + // it will never be used. + block := offset_to_block_backwards(raw_data(pool), BLOCK_HEADER_OVERHEAD) + + block_set_size(block, pool_bytes) + block_set_free(block) + block_set_prev_used(block) + block_insert(control, block) + + // Split the block to create a zero-size sentinel block + next := block_link_next(block) + block_set_size(next, 0) + block_set_used(next) + block_set_prev_free(next) + return +} + +pool_remove :: proc(control: ^Allocator, pool: []u8) { + block := offset_to_block_backwards(raw_data(pool), BLOCK_HEADER_OVERHEAD) + + assert(block_is_free(block), "Block should be free") + assert(!block_is_free(block_next(block)), "Next block should not be free") + assert(block_size(block_next(block)) == 0, "Next block size should be zero") + + fl, sl := mapping_insert(block_size(block)) + remove_free_block(control, block, fl, sl) +} + +@(require_results) +alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) { + assert(control != nil) + adjust := adjust_request_size(size, ALIGN_SIZE) + + GAP_MINIMUM :: size_of(Block_Header) + size_with_gap := adjust_request_size(adjust + align + GAP_MINIMUM, align) + + aligned_size := size_with_gap if adjust != 0 && align > ALIGN_SIZE else adjust + if aligned_size == 0 && size > 0 { + return nil, .Out_Of_Memory + } + + block := block_locate_free(control, aligned_size) + if block == nil { + return nil, .Out_Of_Memory + } + ptr := block_to_ptr(block) + aligned := align_ptr(ptr, align) + gap := uint(int(uintptr(aligned)) - int(uintptr(ptr))) + + if gap != 0 && gap < GAP_MINIMUM { + gap_remain := GAP_MINIMUM - gap + offset := uintptr(max(gap_remain, align)) + next_aligned := rawptr(uintptr(aligned) + offset) + + aligned = align_ptr(next_aligned, align) + + gap = uint(int(uintptr(aligned)) - int(uintptr(ptr))) + } + + if gap != 0 { + assert(gap >= GAP_MINIMUM, "gap size too small") + block = block_trim_free_leading(control, block, gap) + } + + return block_prepare_used(control, block, adjust) +} + +@(require_results) +alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) { + res, err = alloc_bytes_non_zeroed(control, size, align) + if err == nil { + intrinsics.mem_zero(raw_data(res), len(res)) + } + return +} + + +free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) { + assert(control != nil) + // `size` is currently ignored + if ptr == nil { + return + } + + block := block_from_ptr(ptr) + assert(!block_is_free(block), "block already marked as free") // double free + block_mark_as_free(block) + block = block_merge_prev(control, block) + block = block_merge_next(control, block) + block_insert(control, block) +} + + +@(require_results) +resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) { + assert(control != nil) + if ptr != nil && new_size == 0 { + free_with_size(control, ptr, old_size) + return + } else if ptr == nil { + return alloc_bytes(control, new_size, alignment) + } + + block := block_from_ptr(ptr) + next := block_next(block) + + curr_size := block_size(block) + combined := curr_size + block_size(next) + BLOCK_HEADER_OVERHEAD + adjust := adjust_request_size(new_size, max(ALIGN_SIZE, alignment)) + + assert(!block_is_free(block), "block already marked as free") // double free + + min_size := min(curr_size, new_size, old_size) + + if adjust > curr_size && (!block_is_free(next) || adjust > combined) { + res = alloc_bytes(control, new_size, alignment) or_return + if res != nil { + copy(res, ([^]byte)(ptr)[:min_size]) + free_with_size(control, ptr, curr_size) + } + return + } + if adjust > curr_size { + _ = block_merge_next(control, block) + block_mark_as_used(block) + } + + block_trim_used(control, block, adjust) + res = ([^]byte)(ptr)[:new_size] + + if min_size < new_size { + to_zero := ([^]byte)(ptr)[min_size:new_size] + runtime.mem_zero(raw_data(to_zero), len(to_zero)) + } + return +} + +@(require_results) +resize_non_zeroed :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) { + assert(control != nil) + if ptr != nil && new_size == 0 { + free_with_size(control, ptr, old_size) + return + } else if ptr == nil { + return alloc_bytes_non_zeroed(control, new_size, alignment) + } + + block := block_from_ptr(ptr) + next := block_next(block) + + curr_size := block_size(block) + combined := curr_size + block_size(next) + BLOCK_HEADER_OVERHEAD + adjust := adjust_request_size(new_size, max(ALIGN_SIZE, alignment)) + + assert(!block_is_free(block), "block already marked as free") // double free + + min_size := min(curr_size, new_size, old_size) + + if adjust > curr_size && (!block_is_free(next) || adjust > combined) { + res = alloc_bytes_non_zeroed(control, new_size, alignment) or_return + if res != nil { + copy(res, ([^]byte)(ptr)[:min_size]) + free_with_size(control, ptr, old_size) + } + return + } + + if adjust > curr_size { + _ = block_merge_next(control, block) + block_mark_as_used(block) + } + + block_trim_used(control, block, adjust) + res = ([^]byte)(ptr)[:new_size] + return +} diff --git a/core/mem/tracking_allocator.odin b/core/mem/tracking_allocator.odin index d6d189731..1b57e5fb4 100644 --- a/core/mem/tracking_allocator.odin +++ b/core/mem/tracking_allocator.odin @@ -22,6 +22,13 @@ Tracking_Allocator :: struct { bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry, mutex: sync.Mutex, clear_on_free_all: bool, + + total_memory_allocated: i64, + total_allocation_count: i64, + total_memory_freed: i64, + total_free_count: i64, + peak_memory_allocated: i64, + current_memory_allocated: i64, } tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) { @@ -40,13 +47,28 @@ tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) { } +// Clear only the current allocation data while keeping the totals intact. tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { sync.mutex_lock(&t.mutex) clear(&t.allocation_map) clear(&t.bad_free_array) + t.current_memory_allocated = 0 sync.mutex_unlock(&t.mutex) } +// Reset all of a Tracking Allocator's allocation data back to zero. +tracking_allocator_reset :: proc(t: ^Tracking_Allocator) { + sync.mutex_lock(&t.mutex) + clear(&t.allocation_map) + clear(&t.bad_free_array) + t.total_memory_allocated = 0 + t.total_allocation_count = 0 + t.total_memory_freed = 0 + t.total_free_count = 0 + t.peak_memory_allocated = 0 + t.current_memory_allocated = 0 + sync.mutex_unlock(&t.mutex) +} @(require_results) tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { @@ -59,6 +81,21 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> (result: []byte, err: Allocator_Error) { + track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { + data.total_memory_allocated += i64(entry.size) + data.total_allocation_count += 1 + data.current_memory_allocated += i64(entry.size) + if data.current_memory_allocated > data.peak_memory_allocated { + data.peak_memory_allocated = data.current_memory_allocated + } + } + + track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { + data.total_memory_freed += i64(entry.size) + data.total_free_count += 1 + data.current_memory_allocated -= i64(entry.size) + } + data := (^Tracking_Allocator)(allocator_data) sync.mutex_guard(&data.mutex) @@ -100,13 +137,21 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, err = err, location = loc, } + track_alloc(data, &data.allocation_map[result_ptr]) case .Free: + if old_memory != nil && old_memory in data.allocation_map { + track_free(data, &data.allocation_map[old_memory]) + } delete_key(&data.allocation_map, old_memory) case .Free_All: if data.clear_on_free_all { clear_map(&data.allocation_map) + data.current_memory_allocated = 0 } case .Resize, .Resize_Non_Zeroed: + if old_memory != nil && old_memory in data.allocation_map { + track_free(data, &data.allocation_map[old_memory]) + } if old_memory != result_ptr { delete_key(&data.allocation_map, old_memory) } @@ -118,6 +163,7 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, err = err, location = loc, } + track_alloc(data, &data.allocation_map[result_ptr]) case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) diff --git a/core/mem/virtual/virtual_linux.odin b/core/mem/virtual/virtual_linux.odin index 816a8bb84..0b4532baa 100644 --- a/core/mem/virtual/virtual_linux.odin +++ b/core/mem/virtual/virtual_linux.odin @@ -36,9 +36,9 @@ _release :: proc "contextless" (data: rawptr, size: uint) { _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { pflags: linux.Mem_Protection pflags = {} - if .Read in flags { pflags |= {.READ} } - if .Write in flags { pflags |= {.WRITE} } - if .Execute in flags { pflags |= {.EXEC} } + if .Read in flags { pflags += {.READ} } + if .Write in flags { pflags += {.WRITE} } + if .Execute in flags { pflags += {.EXEC} } errno := linux.mprotect(data, size, pflags) return errno == .NONE } diff --git a/core/mem/virtual/virtual_bsd.odin b/core/mem/virtual/virtual_other.odin similarity index 92% rename from core/mem/virtual/virtual_bsd.odin rename to core/mem/virtual/virtual_other.odin index 7568084c0..96d9683c4 100644 --- a/core/mem/virtual/virtual_bsd.odin +++ b/core/mem/virtual/virtual_other.odin @@ -1,9 +1,9 @@ -//+build freebsd, openbsd //+private +//+build !darwin +//+build !linux +//+build !windows package mem_virtual - - _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { return nil, nil } @@ -11,16 +11,18 @@ _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Err _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { return nil } + _decommit :: proc "contextless" (data: rawptr, size: uint) { } + _release :: proc "contextless" (data: rawptr, size: uint) { } + _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { return false } _platform_memory_init :: proc() { - } _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { diff --git a/core/net/addr.odin b/core/net/addr.odin index c01724d99..eed3fb3b9 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -370,7 +370,7 @@ parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, val |= u16(ipv4[3]) piece_values[7] = u16be(val) } - return transmute(IP6_Address)piece_values, true + return IP6_Address(piece_values), true } /* @@ -522,10 +522,9 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> run := Zero_Run{-1, -1} best := Zero_Run{-1, -1} - addr := transmute([8]u16be)v last := u16be(1) - for val, i in addr { + for val, i in v { /* If we encounter adjacent zeroes, then start a new run if not already in one. Also remember the rightmost index regardless, because it'll be the new @@ -559,7 +558,7 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> last = val } - for val, i in addr { + for val, i in v { if best.start == i || best.end == i { // For the left and right side of the best zero run, print a `:`. fmt.sbprint(&b, ":") diff --git a/core/net/common.odin b/core/net/common.odin index 2a6f44602..db969eab8 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -137,8 +137,8 @@ IP4_Address :: distinct [4]u8 IP6_Address :: distinct [8]u16be Address :: union {IP4_Address, IP6_Address} -IP4_Loopback := IP4_Address{127, 0, 0, 1} -IP6_Loopback := IP6_Address{0, 0, 0, 0, 0, 0, 0, 1} +IP4_Loopback :: IP4_Address{127, 0, 0, 1} +IP6_Loopback :: IP6_Address{0, 0, 0, 0, 0, 0, 0, 1} IP4_Any := IP4_Address{} IP6_Any := IP6_Address{} diff --git a/core/net/dns.odin b/core/net/dns.odin index 48cd8bf29..408fd201b 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -30,7 +30,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 { +} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{ resolv_conf = "/etc/resolv.conf", hosts_file = "/etc/hosts", @@ -854,4 +854,4 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator } return _records[:], true -} \ No newline at end of file +} diff --git a/core/net/errors_windows.odin b/core/net/errors_windows.odin index 0538c2b82..f63f17f4e 100644 --- a/core/net/errors_windows.odin +++ b/core/net/errors_windows.odin @@ -135,6 +135,7 @@ TCP_Send_Error :: enum c.int { No_Buffer_Space_Available = win.WSAENOBUFS, Network_Subsystem_Failure = win.WSAENETDOWN, Host_Unreachable = win.WSAEHOSTUNREACH, + Would_Block = win.WSAEWOULDBLOCK, // TODO: verify possible, as not mentioned in docs Offline = win.WSAENETUNREACH, diff --git a/core/net/interface_darwin.odin b/core/net/interface_darwin.odin index 59b0e01c5..e2a9a73ca 100644 --- a/core/net/interface_darwin.odin +++ b/core/net/interface_darwin.odin @@ -59,24 +59,21 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: [] switch int(ifaddr.address.family) { case os.AF_INET, os.AF_INET6: address = _sockaddr_basic_to_endpoint(ifaddr.address).address - case: } } if ifaddr.netmask != nil { switch int(ifaddr.netmask.family) { case os.AF_INET, os.AF_INET6: - netmask = Netmask(_sockaddr_basic_to_endpoint(ifaddr.netmask).address) - case: + netmask = Netmask(_sockaddr_basic_to_endpoint(ifaddr.netmask).address) } } if ifaddr.broadcast_or_dest != nil && .BROADCAST in ifaddr.flags { switch int(ifaddr.broadcast_or_dest.family) { case os.AF_INET, os.AF_INET6: - broadcast := _sockaddr_basic_to_endpoint(ifaddr.broadcast_or_dest).address - append(&iface.multicast, broadcast) - case: + broadcast := _sockaddr_basic_to_endpoint(ifaddr.broadcast_or_dest).address + append(&iface.multicast, broadcast) } } @@ -91,19 +88,19 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: [] /* TODO: Refine this based on the type of adapter. */ - state := Link_State{} + state := Link_State{} - if .UP in ifaddr.flags { - state |= {.Up} - } + if .UP in ifaddr.flags { + state += {.Up} + } - /*if .DORMANT in ifaddr.flags { - state |= {.Dormant} - }*/ + /*if .DORMANT in ifaddr.flags { + state |= {.Dormant} + }*/ - if .LOOPBACK in ifaddr.flags { - state |= {.Loopback} - } + if .LOOPBACK in ifaddr.flags { + state += {.Loopback} + } iface.link.state = state } diff --git a/core/net/interface_windows.odin b/core/net/interface_windows.odin index f8bac253a..b9abcff48 100644 --- a/core/net/interface_windows.odin +++ b/core/net/interface_windows.odin @@ -24,42 +24,42 @@ import strings "core:strings" _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { context.allocator = allocator - buf: []u8 - defer delete(buf) + buf: []u8 + defer delete(buf) - buf_size: u32 - res: u32 + buf_size: u32 + res: u32 - gaa: for _ in 1..=MAX_INTERFACE_ENUMERATION_TRIES { - res = sys.get_adapters_addresses( - .Unspecified, // Return both IPv4 and IPv6 adapters. + gaa: for _ in 1..=MAX_INTERFACE_ENUMERATION_TRIES { + res = sys.get_adapters_addresses( + .Unspecified, // Return both IPv4 and IPv6 adapters. sys.GAA_Flags{ .Include_Prefix, // (XP SP1+) Return a list of IP address prefixes on this adapter. When this flag is set, IP address prefixes are returned for both IPv6 and IPv4 addresses. .Include_Gateways, // (Vista+) Return the addresses of default gateways. .Include_Tunnel_Binding_Order, // (Vista+) Return the adapter addresses sorted in tunnel binding order. }, - nil, // Reserved - (^sys.IP_Adapter_Addresses)(raw_data(buf)), - &buf_size, - ) + nil, // Reserved + (^sys.IP_Adapter_Addresses)(raw_data(buf)), + &buf_size, + ) - switch res { - case 111: // ERROR_BUFFER_OVERFLOW: - delete(buf) - buf = make([]u8, buf_size) - case 0: - break gaa - case: - return {}, Platform_Error(res) - } - } + switch res { + case 111: // ERROR_BUFFER_OVERFLOW: + delete(buf) + buf = make([]u8, buf_size) + case 0: + break gaa + case: + return {}, Platform_Error(res) + } + } - if res != 0 { - return {}, .Unable_To_Enumerate_Network_Interfaces - } + if res != 0 { + return {}, .Unable_To_Enumerate_Network_Interfaces + } - _interfaces := make([dynamic]Network_Interface, 0, allocator) - for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next { + _interfaces := make([dynamic]Network_Interface, 0, allocator) + for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next { friendly_name, err1 := sys.wstring_to_utf8(sys.wstring(adapter.FriendlyName), 256, allocator) if err1 != nil { return {}, Platform_Error(err1) } @@ -71,74 +71,74 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: [] interface := Network_Interface{ adapter_name = strings.clone(string(adapter.AdapterName)), - friendly_name = friendly_name, - description = description, - dns_suffix = dns_suffix, + friendly_name = friendly_name, + description = description, + dns_suffix = dns_suffix, - mtu = adapter.MTU, + mtu = adapter.MTU, - link = { + link = { transmit_speed = adapter.TransmitLinkSpeed, receive_speed = adapter.ReceiveLinkSpeed, - }, - } + }, + } - if adapter.PhysicalAddressLength > 0 && adapter.PhysicalAddressLength <= len(adapter.PhysicalAddress) { - interface.physical_address = physical_address_to_string(adapter.PhysicalAddress[:adapter.PhysicalAddressLength]) - } + if adapter.PhysicalAddressLength > 0 && adapter.PhysicalAddressLength <= len(adapter.PhysicalAddress) { + interface.physical_address = physical_address_to_string(adapter.PhysicalAddress[:adapter.PhysicalAddressLength]) + } - for u_addr := (^sys.IP_ADAPTER_UNICAST_ADDRESS_LH)(adapter.FirstUnicastAddress); u_addr != nil; u_addr = u_addr.Next { - win_addr := parse_socket_address(u_addr.Address) + for u_addr := (^sys.IP_ADAPTER_UNICAST_ADDRESS_LH)(adapter.FirstUnicastAddress); u_addr != nil; u_addr = u_addr.Next { + win_addr := parse_socket_address(u_addr.Address) - lease := Lease{ - address = win_addr.address, - origin = { - prefix = Prefix_Origin(u_addr.PrefixOrigin), - suffix = Suffix_Origin(u_addr.SuffixOrigin), - }, - lifetime = { - valid = u_addr.ValidLifetime, - preferred = u_addr.PreferredLifetime, - lease = u_addr.LeaseLifetime, - }, - address_duplication = Address_Duplication(u_addr.DadState), - } - append(&interface.unicast, lease) - } + lease := Lease{ + address = win_addr.address, + origin = { + prefix = Prefix_Origin(u_addr.PrefixOrigin), + suffix = Suffix_Origin(u_addr.SuffixOrigin), + }, + lifetime = { + valid = u_addr.ValidLifetime, + preferred = u_addr.PreferredLifetime, + lease = u_addr.LeaseLifetime, + }, + address_duplication = Address_Duplication(u_addr.DadState), + } + append(&interface.unicast, lease) + } - for a_addr := (^sys.IP_ADAPTER_ANYCAST_ADDRESS_XP)(adapter.FirstAnycastAddress); a_addr != nil; a_addr = a_addr.Next { - addr := parse_socket_address(a_addr.Address) - append(&interface.anycast, addr.address) - } + for a_addr := (^sys.IP_ADAPTER_ANYCAST_ADDRESS_XP)(adapter.FirstAnycastAddress); a_addr != nil; a_addr = a_addr.Next { + addr := parse_socket_address(a_addr.Address) + append(&interface.anycast, addr.address) + } - for m_addr := (^sys.IP_ADAPTER_MULTICAST_ADDRESS_XP)(adapter.FirstMulticastAddress); m_addr != nil; m_addr = m_addr.Next { - addr := parse_socket_address(m_addr.Address) - append(&interface.multicast, addr.address) - } + for m_addr := (^sys.IP_ADAPTER_MULTICAST_ADDRESS_XP)(adapter.FirstMulticastAddress); m_addr != nil; m_addr = m_addr.Next { + addr := parse_socket_address(m_addr.Address) + append(&interface.multicast, addr.address) + } - for g_addr := (^sys.IP_ADAPTER_GATEWAY_ADDRESS_LH)(adapter.FirstGatewayAddress); g_addr != nil; g_addr = g_addr.Next { - addr := parse_socket_address(g_addr.Address) - append(&interface.gateways, addr.address) - } + for g_addr := (^sys.IP_ADAPTER_GATEWAY_ADDRESS_LH)(adapter.FirstGatewayAddress); g_addr != nil; g_addr = g_addr.Next { + addr := parse_socket_address(g_addr.Address) + append(&interface.gateways, addr.address) + } interface.dhcp_v4 = parse_socket_address(adapter.Dhcpv4Server).address interface.dhcp_v6 = parse_socket_address(adapter.Dhcpv6Server).address - switch adapter.OperStatus { - case .Up: interface.link.state = {.Up} - case .Down: interface.link.state = {.Down} - case .Testing: interface.link.state = {.Testing} - case .Dormant: interface.link.state = {.Dormant} - case .NotPresent: interface.link.state = {.Not_Present} - case .LowerLayerDown: interface.link.state = {.Lower_Layer_Down} - case .Unknown: fallthrough - case: interface.link.state = {} - } + switch adapter.OperStatus { + case .Up: interface.link.state = {.Up} + case .Down: interface.link.state = {.Down} + case .Testing: interface.link.state = {.Testing} + case .Dormant: interface.link.state = {.Dormant} + case .NotPresent: interface.link.state = {.Not_Present} + case .LowerLayerDown: interface.link.state = {.Lower_Layer_Down} + case .Unknown: fallthrough + case: interface.link.state = {} + } - interface.tunnel_type = Tunnel_Type(adapter.TunnelType) + interface.tunnel_type = Tunnel_Type(adapter.TunnelType) - append(&_interfaces, interface) - } + append(&_interfaces, interface) + } return _interfaces[:], {} } diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin index ba86f1005..fc422b3a9 100644 --- a/core/net/socket_darwin.odin +++ b/core/net/socket_darwin.odin @@ -274,8 +274,7 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca .Linger, .Send_Timeout, .Receive_Timeout: - t, ok := value.(time.Duration) - if !ok do panic("set_option() value must be a time.Duration here", loc) + t := value.(time.Duration) or_else panic("set_option() value must be a time.Duration here", loc) micros := i64(time.duration_microseconds(t)) timeval_value.microseconds = int(micros % 1e6) @@ -320,7 +319,7 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E } if should_block { - flags &= ~int(os.O_NONBLOCK) + flags &~= int(os.O_NONBLOCK) } else { flags |= int(os.O_NONBLOCK) } diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index a4d75b92b..350d3947c 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -80,14 +80,14 @@ _unwrap_os_addr :: proc "contextless" (endpoint: Endpoint)->(linux.Sock_Addr_Any ipv4 = { sin_family = .INET, sin_port = u16be(endpoint.port), - sin_addr = transmute([4]u8) endpoint.address.(IP4_Address), + sin_addr = ([4]u8)(endpoint.address.(IP4_Address)), }, } case IP6_Address: return { ipv6 = { sin6_port = u16be(endpoint.port), - sin6_addr = transmute([16]u8) endpoint.address.(IP6_Address), + sin6_addr = transmute([16]u8)endpoint.address.(IP6_Address), sin6_family = .INET6, }, } @@ -117,7 +117,7 @@ _wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) { _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Network_Error) { family := _unwrap_os_family(family) proto, socktype := _unwrap_os_proto_socktype(protocol) - sock, errno := linux.socket(family, socktype, {}, proto) + sock, errno := linux.socket(family, socktype, {.CLOEXEC}, proto) if errno != .NONE { return {}, Create_Socket_Error(errno) } @@ -132,7 +132,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio } // Create new TCP socket os_sock: linux.Fd - os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {}, .TCP) + os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {.CLOEXEC}, .TCP) if errno != .NONE { // TODO(flysand): should return invalid file descriptor here casted as TCP_Socket return {}, Create_Socket_Error(errno) @@ -172,7 +172,7 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (TCP_Socket, Network ep_address := _unwrap_os_addr(endpoint) // Create TCP socket os_sock: linux.Fd - os_sock, errno = linux.socket(ep_family, .STREAM, {}, .TCP) + os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP) if errno != .NONE { // TODO(flysand): should return invalid file descriptor here casted as TCP_Socket return {}, Create_Socket_Error(errno) @@ -377,9 +377,9 @@ _set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Network_Err return Set_Blocking_Error(errno) } if should_block { - flags &= ~{.NONBLOCK} + flags -= {.NONBLOCK} } else { - flags |= {.NONBLOCK} + flags += {.NONBLOCK} } errno = linux.fcntl(os_sock, linux.F_SETFL, flags) if errno != .NONE { diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin index 3b9623749..eef0df583 100644 --- a/core/net/socket_windows.odin +++ b/core/net/socket_windows.odin @@ -263,12 +263,15 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca ptr = &bool_value len = size_of(bool_value) case .Linger: - t, ok := value.(time.Duration) - if !ok do panic("set_option() value must be a time.Duration here", loc) + t := value.(time.Duration) or_else panic("set_option() value must be a time.Duration here", loc) num_secs := i64(time.duration_seconds(t)) - if time.Duration(num_secs * 1e9) != t do return .Linger_Only_Supports_Whole_Seconds - if num_secs > i64(max(u16)) do return .Value_Out_Of_Range + if time.Duration(num_secs * 1e9) != t { + return .Linger_Only_Supports_Whole_Seconds + } + if num_secs > i64(max(u16)) { + return .Value_Out_Of_Range + } linger_value.l_onoff = 1 linger_value.l_linger = c.ushort(num_secs) @@ -277,8 +280,7 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca case .Receive_Timeout, .Send_Timeout: - t, ok := value.(time.Duration) - if !ok do panic("set_option() value must be a time.Duration here", loc) + t := value.(time.Duration) or_else panic("set_option() value must be a time.Duration here", loc) int_value = i32(time.duration_milliseconds(t)) ptr = &int_value diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index be541befa..0ae822e21 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -513,6 +513,7 @@ Package_Decl :: struct { Import_Decl :: struct { using node: Decl, docs: ^Comment_Group, + attributes: [dynamic]^Attribute, // dynamic as parsing will add to them lazily is_using: bool, import_tok: tokenizer.Token, name: tokenizer.Token, @@ -538,7 +539,7 @@ Foreign_Import_Decl :: struct { import_tok: tokenizer.Token, name: ^Ident, collection_name: string, - fullpaths: []string, + fullpaths: []^Expr, comment: ^Comment_Group, } @@ -598,6 +599,7 @@ Field_Flag :: enum { Subtype, By_Ptr, No_Broadcast, + No_Capture, Results, Tags, @@ -618,6 +620,7 @@ field_flag_strings := [Field_Flag]string{ .Subtype = "#subtype", .By_Ptr = "#by_ptr", .No_Broadcast = "#no_broadcast", + .No_Capture = "#no_capture", .Results = "results", .Tags = "field tag", @@ -633,6 +636,7 @@ field_hash_flag_strings := []struct{key: string, flag: Field_Flag}{ {"subtype", .Subtype}, {"by_ptr", .By_Ptr}, {"no_broadcast", .No_Broadcast}, + {"no_capture", .No_Capture}, } @@ -753,7 +757,7 @@ Array_Type :: struct { using node: Expr, open: tokenizer.Pos, tag: ^Expr, - len: ^Expr, // Ellipsis node for [?]T arrray types, nil for slice types + len: ^Expr, // Ellipsis node for [?]T array types, nil for slice types close: tokenizer.Pos, elem: ^Expr, } diff --git a/core/odin/ast/clone.odin b/core/odin/ast/clone.odin index bca740dd4..b0a1673b2 100644 --- a/core/odin/ast/clone.odin +++ b/core/odin/ast/clone.odin @@ -278,7 +278,9 @@ clone_node :: proc(node: ^Node) -> ^Node { r.foreign_library = clone(r.foreign_library) r.body = clone(r.body) case ^Foreign_Import_Decl: + r.attributes = clone_dynamic_array(r.attributes) r.name = auto_cast clone(r.name) + r.fullpaths = clone_array(r.fullpaths) case ^Proc_Group: r.args = clone(r.args) case ^Attribute: diff --git a/core/odin/ast/walk.odin b/core/odin/ast/walk.odin index 63107a2e2..7304f237c 100644 --- a/core/odin/ast/walk.odin +++ b/core/odin/ast/walk.odin @@ -320,6 +320,7 @@ walk :: proc(v: ^Visitor, node: ^Node) { if n.comment != nil { walk(v, n.comment) } + walk_expr_list(v, n.fullpaths) case ^Proc_Group: walk_expr_list(v, n.args) diff --git a/core/odin/format/deprecated.odin b/core/odin/format/deprecated.odin new file mode 100644 index 000000000..3d8fea957 --- /dev/null +++ b/core/odin/format/deprecated.odin @@ -0,0 +1,3 @@ +package odin_format + +#panic("The format package has been deprecated. Please look at https://github.com/DanielGavin/ols") \ No newline at end of file diff --git a/core/odin/format/format.odin b/core/odin/format/format.odin deleted file mode 100644 index 66a7eb5f3..000000000 --- a/core/odin/format/format.odin +++ /dev/null @@ -1,41 +0,0 @@ -package odin_format - -import "core:odin/printer" -import "core:odin/parser" -import "core:odin/ast" - -default_style := printer.default_style - -simplify :: proc(file: ^ast.File) { - -} - -format :: proc(filepath: string, source: string, config: printer.Config, parser_flags := parser.Flags{}, allocator := context.allocator) -> (string, bool) { - config := config - - pkg := ast.Package { - kind = .Normal, - } - - file := ast.File { - pkg = &pkg, - src = source, - fullpath = filepath, - } - - config.newline_limit = clamp(config.newline_limit, 0, 16) - config.spaces = clamp(config.spaces, 1, 16) - config.align_length_break = clamp(config.align_length_break, 0, 64) - - p := parser.default_parser(parser_flags) - - ok := parser.parse_file(&p, &file) - - if !ok || file.syntax_error_count > 0 { - return {}, false - } - - prnt := printer.make_printer(config, allocator) - - return printer.print(&prnt, &file), true -} diff --git a/core/odin/parser/parse_files.odin b/core/odin/parser/parse_files.odin index 6452faf4c..5f455c749 100644 --- a/core/odin/parser/parse_files.odin +++ b/core/odin/parser/parse_files.odin @@ -6,6 +6,7 @@ import "core:path/filepath" import "core:fmt" import "core:os" import "core:slice" +import "core:strings" collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) { NO_POS :: tokenizer.Pos{} @@ -32,11 +33,18 @@ collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) { if !ok { return } + src, ok = os.read_entire_file(fullpath) if !ok { delete(fullpath) return } + if strings.trim_space(string(src)) == "" { + delete(fullpath) + delete(src) + continue + } + file := ast.new(ast.File, NO_POS, NO_POS) file.pkg = pkg file.src = string(src) @@ -69,7 +77,9 @@ parse_package :: proc(pkg: ^ast.Package, p: ^Parser = nil) -> bool { if !parse_file(p, file) { ok = false } - if pkg.name == "" { + if file.pkg_decl == nil { + error(p, p.curr_tok.pos, "Expected a package declaration at the start of the file") + } else if pkg.name == "" { pkg.name = file.pkg_decl.name } else if pkg.name != file.pkg_decl.name { error(p, file.pkg_decl.pos, "different package name, expected '%s', got '%s'", pkg.name, file.pkg_decl.name) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index b2ffd3888..4d045f785 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -308,7 +308,7 @@ consume_comment_group :: proc(p: ^Parser, n: int) -> (comments: ^ast.Comment_Gro end_line = p.curr_tok.pos.line for p.curr_tok.kind == .Comment && p.curr_tok.pos.line <= end_line+n { - comment: tokenizer.Token + comment: tokenizer.Token comment, end_line = consume_comment(p) append(&list, comment) } @@ -689,7 +689,12 @@ parse_when_stmt :: proc(p: ^Parser) -> ^ast.When_Stmt { prev_level := p.expr_level p.expr_level = -1 + prev_allow_in_expr := p.allow_in_expr + p.allow_in_expr = true + cond = parse_expr(p, false) + + p.allow_in_expr = prev_allow_in_expr p.expr_level = prev_level if cond == nil { @@ -1103,6 +1108,9 @@ parse_attribute :: proc(p: ^Parser, tok: tokenizer.Token, open_kind, close_kind: case ^ast.Foreign_Import_Decl: if d.docs == nil { d.docs = docs } append(&d.attributes, attribute) + case ^ast.Import_Decl: + if d.docs == nil { d.docs = docs } + append(&d.attributes, attribute) case: error(p, decl.pos, "expected a value or foreign declaration after an attribute") free(attribute) @@ -1190,12 +1198,12 @@ parse_foreign_decl :: proc(p: ^Parser) -> ^ast.Decl { error(p, name.pos, "illegal foreign import name: '_'") } - fullpaths: [dynamic]string + fullpaths: [dynamic]^ast.Expr if allow_token(p, .Open_Brace) { for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF { - path := expect_token(p, .String) - append(&fullpaths, path.text) + path := parse_expr(p, false) + append(&fullpaths, path) allow_token(p, .Comma) or_break } @@ -1203,7 +1211,9 @@ parse_foreign_decl :: proc(p: ^Parser) -> ^ast.Decl { } else { path := expect_token(p, .String) reserve(&fullpaths, 1) - append(&fullpaths, path.text) + bl := ast.new(ast.Basic_Lit, path.pos, end_pos(path)) + bl.tok = path + append(&fullpaths, bl) } if len(fullpaths) == 0 { @@ -1305,8 +1315,8 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { // Unary Expressions .Add, .Sub, .Xor, .Not, .And: - s := parse_simple_stmt(p, {Stmt_Allow_Flag.Label}) - expect_semicolon(p, s) + s := parse_simple_stmt(p, {Stmt_Allow_Flag.Label}) + expect_semicolon(p, s) return s @@ -1453,7 +1463,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { case "unroll": return parse_unrolled_for_loop(p, tag) case "reverse": - stmt := parse_for_stmt(p) + stmt := parse_stmt(p) if range, is_range := stmt.derived.(^ast.Range_Stmt); is_range { if range.reverse { @@ -1843,7 +1853,7 @@ parse_ident_list :: proc(p: ^Parser, allow_poly_names: bool) -> []^ast.Expr { } if p.curr_tok.kind != .Comma || p.curr_tok.kind == .EOF { - break + break } advance_token(p) } @@ -2117,7 +2127,9 @@ parse_proc_type :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Proc_Type { } expect_token(p, .Open_Paren) + p.expr_level += 1 params, _ := parse_field_list(p, .Close_Paren, ast.Field_Flags_Signature_Params) + p.expr_level -= 1 expect_closing_parentheses_of_field_list(p) results, diverging := parse_results(p) @@ -2167,22 +2179,25 @@ parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^ } } - #partial switch e in ast.strip_or_return_expr(expr).derived_expr { - case ^ast.Proc_Lit: - if e.inlining != .None && e.inlining != pi { - error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal") + if expr != nil { + #partial switch e in ast.strip_or_return_expr(expr).derived_expr { + case ^ast.Proc_Lit: + if e.inlining != .None && e.inlining != pi { + error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal") + } + e.inlining = pi + return expr + case ^ast.Call_Expr: + if e.inlining != .None && e.inlining != pi { + error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call") + } + e.inlining = pi + return expr } - e.inlining = pi - case ^ast.Call_Expr: - if e.inlining != .None && e.inlining != pi { - error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call") - } - e.inlining = pi - case: - error(p, tok.pos, "'%s' must be followed by a procedure literal or call", tok.text) - return ast.new(ast.Bad_Expr, tok.pos, expr) } - return expr + + error(p, tok.pos, "'%s' must be followed by a procedure literal or call", tok.text) + return ast.new(ast.Bad_Expr, tok.pos, expr) } parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { @@ -2204,10 +2219,10 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { case .Integer, .Float, .Imag, .Rune, .String: - tok := advance_token(p) - bl := ast.new(ast.Basic_Lit, tok.pos, end_pos(tok)) - bl.tok = tok - return bl + tok := advance_token(p) + bl := ast.new(ast.Basic_Lit, tok.pos, end_pos(tok)) + bl.tok = tok + return bl case .Open_Brace: if !lhs { @@ -2246,18 +2261,18 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { hp.type = type return hp - case "file", "line", "procedure", "caller_location": + case "file", "directory", "line", "procedure", "caller_location": bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) bd.tok = tok bd.name = name.text return bd - case "location", "load", "assert", "defined", "config": + + case "location", "exists", "load", "load_directory", "load_hash", "hash", "assert", "panic", "defined", "config": bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) bd.tok = tok bd.name = name.text return parse_call_expr(p, bd) - case "soa": bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) bd.tok = tok @@ -2995,7 +3010,7 @@ parse_literal_value :: proc(p: ^Parser, type: ^ast.Expr) -> ^ast.Comp_Lit { } p.expr_level -= 1 - skip_possible_newline(p) + skip_possible_newline(p) close := expect_closing_brace_of_field_list(p) pos := type.pos if type != nil else open.pos @@ -3511,6 +3526,25 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt { case op.kind == .Colon: expect_token_after(p, .Colon, "identifier list") if .Label in flags && len(lhs) == 1 { + is_partial := false + is_reverse := false + + partial_token: tokenizer.Token + if p.curr_tok.kind == .Hash { + name := peek_token(p) + if name.kind == .Ident && name.text == "partial" && + peek_token(p, 1).kind == .Switch { + partial_token = expect_token(p, .Hash) + expect_token(p, .Ident) + is_partial = true + } else if name.kind == .Ident && name.text == "reverse" && + peek_token(p, 1).kind == .For { + partial_token = expect_token(p, .Hash) + expect_token(p, .Ident) + is_reverse = true + } + } + #partial switch p.curr_tok.kind { case .Open_Brace, .If, .For, .Switch: label := lhs[0] @@ -3525,6 +3559,22 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt { case ^ast.Type_Switch_Stmt: n.label = label case ^ast.Range_Stmt: n.label = label } + + if is_partial { + #partial switch n in stmt.derived_stmt { + case ^ast.Switch_Stmt: n.partial = true + case ^ast.Type_Switch_Stmt: n.partial = true + case: + error(p, partial_token.pos, "incorrect use of directive, use '%s: #partial switch'", partial_token.text) + } + } + if is_reverse { + #partial switch n in stmt.derived_stmt { + case ^ast.Range_Stmt: n.reverse = true + case: + error(p, partial_token.pos, "incorrect use of directive, use '%s: #reverse for'", partial_token.text) + } + } } return stmt diff --git a/core/odin/printer/deprecated.odin b/core/odin/printer/deprecated.odin new file mode 100644 index 000000000..6c053d1bc --- /dev/null +++ b/core/odin/printer/deprecated.odin @@ -0,0 +1,3 @@ +package odin_printer + +#panic("The printer package has been deprecated. Please look at https://github.com/DanielGavin/ols") \ No newline at end of file diff --git a/core/odin/printer/printer.odin b/core/odin/printer/printer.odin deleted file mode 100644 index ce75352bd..000000000 --- a/core/odin/printer/printer.odin +++ /dev/null @@ -1,922 +0,0 @@ -package odin_printer - -import "core:odin/ast" -import "core:odin/tokenizer" -import "core:strings" -import "core:fmt" -import "core:mem" - -Type_Enum :: enum {Line_Comment, Value_Decl, Switch_Stmt, Struct, Assign, Call, Enum, If, For, Proc_Lit} - -Line_Type :: bit_set[Type_Enum] - -/* - Represents an unwrapped line -*/ -Line :: struct { - format_tokens: [dynamic]Format_Token, - finalized: bool, - used: bool, - depth: int, - types: Line_Type, //for performance, so you don't have to verify what types are in it by going through the tokens - might give problems when adding linebreaking -} - -/* - Represents a singular token in a unwrapped line -*/ -Format_Token :: struct { - kind: tokenizer.Token_Kind, - text: string, - type: Type_Enum, - spaces_before: int, - parameter_count: int, -} - -Printer :: struct { - string_builder: strings.Builder, - config: Config, - depth: int, //the identation depth - comments: [dynamic]^ast.Comment_Group, - latest_comment_index: int, - allocator: mem.Allocator, - file: ^ast.File, - source_position: tokenizer.Pos, - last_source_position: tokenizer.Pos, - lines: [dynamic]Line, //need to look into a better data structure, one that can handle inserting lines rather than appending - skip_semicolon: bool, - current_line: ^Line, - current_line_index: int, - last_line_index: int, - last_token: ^Format_Token, - merge_next_token: bool, - space_next_token: bool, - debug: bool, -} - -Config :: struct { - spaces: int, //Spaces per indentation - newline_limit: int, //The limit of newlines between statements and declarations. - tabs: bool, //Enable or disable tabs - convert_do: bool, //Convert all do statements to brace blocks - semicolons: bool, //Enable semicolons - split_multiple_stmts: bool, - align_switch: bool, - brace_style: Brace_Style, - align_assignments: bool, - align_structs: bool, - align_style: Alignment_Style, - align_enums: bool, - align_length_break: int, - indent_cases: bool, - newline_style: Newline_Style, -} - -Brace_Style :: enum { - _1TBS, - Allman, - Stroustrup, - K_And_R, -} - -Block_Type :: enum { - None, - If_Stmt, - Proc, - Generic, - Comp_Lit, - Switch_Stmt, -} - -Alignment_Style :: enum { - Align_On_Type_And_Equals, - Align_On_Colon_And_Equals, -} - -Newline_Style :: enum { - CRLF, - LF, -} - -default_style := Config { - spaces = 4, - newline_limit = 2, - convert_do = false, - semicolons = false, - tabs = true, - brace_style = ._1TBS, - split_multiple_stmts = true, - align_assignments = true, - align_style = .Align_On_Type_And_Equals, - indent_cases = false, - align_switch = true, - align_structs = true, - align_enums = true, - newline_style = .CRLF, - align_length_break = 9, -} - -make_printer :: proc(config: Config, allocator := context.allocator) -> Printer { - return { - config = config, - allocator = allocator, - debug = false, - } -} - -print :: proc(p: ^Printer, file: ^ast.File) -> string { - p.comments = file.comments - - if len(file.decls) > 0 { - p.lines = make([dynamic]Line, 0, (file.decls[len(file.decls) - 1].end.line - file.decls[0].pos.line) * 2, context.temp_allocator) - } - - set_source_position(p, file.pkg_token.pos) - - p.last_source_position.line = 1 - - set_line(p, 0) - - push_generic_token(p, .Package, 0) - push_ident_token(p, file.pkg_name, 1) - - for decl in file.decls { - visit_decl(p, cast(^ast.Decl)decl) - } - - if len(p.comments) > 0 { - infinite := p.comments[len(p.comments) - 1].end - infinite.offset = 9999999 - push_comments(p, infinite) - } - - fix_lines(p) - - builder := strings.builder_make(0, 5 * mem.Megabyte, p.allocator) - - last_line := 0 - - newline: string - - if p.config.newline_style == .LF { - newline = "\n" - } else { - newline = "\r\n" - } - - for line, line_index in p.lines { - diff_line := line_index - last_line - - for i := 0; i < diff_line; i += 1 { - strings.write_string(&builder, newline) - } - - if p.config.tabs { - for i := 0; i < line.depth; i += 1 { - strings.write_byte(&builder, '\t') - } - } else { - for i := 0; i < line.depth * p.config.spaces; i += 1 { - strings.write_byte(&builder, ' ') - } - } - - if p.debug { - strings.write_string(&builder, fmt.tprintf("line %v: ", line_index)) - } - - for format_token in line.format_tokens { - - for i := 0; i < format_token.spaces_before; i += 1 { - strings.write_byte(&builder, ' ') - } - - strings.write_string(&builder, format_token.text) - } - - last_line = line_index - } - - strings.write_string(&builder, newline) - - return strings.to_string(builder) -} - -fix_lines :: proc(p: ^Printer) { - align_var_decls(p) - format_generic(p) - align_comments(p) //align them last since they rely on the other alignments -} - -format_value_decl :: proc(p: ^Printer, index: int) { - - eq_found := false - eq_token: Format_Token - eq_line: int - largest := 0 - - found_eq: for line, line_index in p.lines[index:] { - for format_token in line.format_tokens { - - largest += len(format_token.text) + format_token.spaces_before - - if format_token.kind == .Eq { - eq_token = format_token - eq_line = line_index + index - eq_found = true - break found_eq - } - } - } - - if !eq_found { - return - } - - align_next := false - - //check to see if there is a binary operator in the last token(this is guaranteed by the ast visit), otherwise it's not multilined - for line in p.lines[eq_line:] { - - if len(line.format_tokens) == 0 { - break - } - - if align_next { - line.format_tokens[0].spaces_before = largest + 1 - align_next = false - } - - kind := find_last_token(line.format_tokens).kind - - if tokenizer.Token_Kind.B_Operator_Begin < kind && kind <= tokenizer.Token_Kind.Cmp_Or { - align_next = true - } - - if !align_next { - break - } - } -} - -find_last_token :: proc(format_tokens: [dynamic]Format_Token) -> Format_Token { - - for i := len(format_tokens) - 1; i >= 0; i -= 1 { - - if format_tokens[i].kind != .Comment { - return format_tokens[i] - } - } - - panic("not possible") -} - -format_assignment :: proc(p: ^Printer, index: int) { -} - -format_call :: proc(p: ^Printer, line_index: int, format_index: int) { - - paren_found := false - paren_token: Format_Token - paren_line: int - paren_token_index: int - largest := 0 - - found_paren: for line, i in p.lines[line_index:] { - for format_token, j in line.format_tokens { - - largest += len(format_token.text) + format_token.spaces_before - - if i == 0 && j < format_index { - continue - } - - if format_token.kind == .Open_Paren && format_token.type == .Call { - paren_token = format_token - paren_line = line_index + i - paren_found = true - paren_token_index = j - break found_paren - } - } - } - - if !paren_found { - panic("Should not be possible") - } - - paren_count := 1 - done := false - - for line in p.lines[paren_line:] { - - if len(line.format_tokens) == 0 { - continue - } - - for format_token, i in line.format_tokens { - - if format_token.kind == .Comment { - continue - } - - if line_index == 0 && i <= paren_token_index { - continue - } - - if format_token.kind == .Open_Paren { - paren_count += 1 - } else if format_token.kind == .Close_Paren { - paren_count -= 1 - } - - if paren_count == 0 { - done = true - } - } - - if line_index != 0 { - line.format_tokens[0].spaces_before = largest - } - - if done { - return - } - } -} - -format_keyword_to_brace :: proc(p: ^Printer, line_index: int, format_index: int, keyword: tokenizer.Token_Kind) { - - keyword_found := false - keyword_token: Format_Token - keyword_line: int - - largest := 0 - brace_count := 0 - done := false - - found_keyword: for line, i in p.lines[line_index:] { - for format_token in line.format_tokens { - - largest += len(format_token.text) + format_token.spaces_before - - if format_token.kind == keyword { - keyword_token = format_token - keyword_line = line_index + i - keyword_found = true - break found_keyword - } - } - } - - if !keyword_found { - panic("Should not be possible") - } - - for line, line_idx in p.lines[keyword_line:] { - - if len(line.format_tokens) == 0 { - continue - } - - for format_token, i in line.format_tokens { - - if format_token.kind == .Comment { - break - } else if format_token.kind == .Undef { - return - } - - if line_idx == 0 && i <= format_index { - continue - } - - if format_token.kind == .Open_Brace { - brace_count += 1 - } else if format_token.kind == .Close_Brace { - brace_count -= 1 - } - - if brace_count == 1 { - done = true - } - } - - if line_idx != 0 { - line.format_tokens[0].spaces_before = largest + 1 - } - - if done { - return - } - } -} - -format_generic :: proc(p: ^Printer) { - next_struct_line := 0 - - for line, line_index in p.lines { - - if len(line.format_tokens) <= 0 { - continue - } - - for format_token, token_index in line.format_tokens { - #partial switch format_token.kind { - case .For, .If, .When, .Switch: - format_keyword_to_brace(p, line_index, token_index, format_token.kind) - case .Proc: - if format_token.type == .Proc_Lit { - format_keyword_to_brace(p, line_index, token_index, format_token.kind) - } - case: - if format_token.type == .Call { - format_call(p, line_index, token_index) - } - } - } - - if .Switch_Stmt in line.types && p.config.align_switch { - align_switch_stmt(p, line_index) - } - - if .Enum in line.types && p.config.align_enums { - align_enum(p, line_index) - } - - if .Struct in line.types && p.config.align_structs && next_struct_line <= 0 { - next_struct_line = align_struct(p, line_index) - } - - if .Value_Decl in line.types { - format_value_decl(p, line_index) - } - - if .Assign in line.types { - format_assignment(p, line_index) - } - - next_struct_line -= 1 - } -} - -align_var_decls :: proc(p: ^Printer) { - - current_line: int - current_typed: bool - current_not_mutable: bool - - largest_lhs := 0 - largest_rhs := 0 - - TokenAndLength :: struct { - format_token: ^Format_Token, - length: int, - } - - colon_tokens := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator) - type_tokens := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator) - equal_tokens := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator) - - for line, line_index in p.lines { - - //It is only possible to align value decls that are one one line, otherwise just ignore them - if .Value_Decl not_in line.types { - continue - } - - typed := true - not_mutable := false - continue_flag := false - - for i := 0; i < len(line.format_tokens); i += 1 { - if line.format_tokens[i].kind == .Colon && line.format_tokens[min(i + 1, len(line.format_tokens) - 1)].kind == .Eq { - typed = false - } - - if line.format_tokens[i].kind == .Colon && line.format_tokens[min(i + 1, len(line.format_tokens) - 1)].kind == .Colon { - not_mutable = true - } - - if line.format_tokens[i].kind == .Union || - line.format_tokens[i].kind == .Enum || - line.format_tokens[i].kind == .Struct || - line.format_tokens[i].kind == .For || - line.format_tokens[i].kind == .If || - line.format_tokens[i].kind == .Comment { - continue_flag = true - } - - //enforced undef is always on the last line, if it exists - if line.format_tokens[i].kind == .Proc && line.format_tokens[len(line.format_tokens)-1].kind != .Undef { - continue_flag = true - } - - } - - if continue_flag { - continue - } - - if line_index != current_line + 1 || typed != current_typed || not_mutable != current_not_mutable { - - if p.config.align_style == .Align_On_Colon_And_Equals || !current_typed || current_not_mutable { - for colon_token in colon_tokens { - colon_token.format_token.spaces_before = largest_lhs - colon_token.length + 1 - } - } else if p.config.align_style == .Align_On_Type_And_Equals { - for type_token in type_tokens { - type_token.format_token.spaces_before = largest_lhs - type_token.length + 1 - } - } - - if current_typed { - for equal_token in equal_tokens { - equal_token.format_token.spaces_before = largest_rhs - equal_token.length + 1 - } - } else { - for equal_token in equal_tokens { - equal_token.format_token.spaces_before = 0 - } - } - - clear(&colon_tokens) - clear(&type_tokens) - clear(&equal_tokens) - - largest_rhs = 0 - largest_lhs = 0 - current_typed = typed - current_not_mutable = not_mutable - } - - current_line = line_index - - current_token_index := 0 - lhs_length := 0 - rhs_length := 0 - - //calcuate the length of lhs of a value decl i.e. `a, b:` - for; current_token_index < len(line.format_tokens); current_token_index += 1 { - - lhs_length += len(line.format_tokens[current_token_index].text) + line.format_tokens[current_token_index].spaces_before - - if line.format_tokens[current_token_index].kind == .Colon { - append(&colon_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index], length = lhs_length}) - - if len(line.format_tokens) > current_token_index && line.format_tokens[current_token_index + 1].kind != .Eq { - append(&type_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index + 1], length = lhs_length}) - } - - current_token_index += 1 - largest_lhs = max(largest_lhs, lhs_length) - break - } - } - - //calcuate the length of the rhs i.e. `[dynamic]int = 123123` - for; current_token_index < len(line.format_tokens); current_token_index += 1 { - - rhs_length += len(line.format_tokens[current_token_index].text) + line.format_tokens[current_token_index].spaces_before - - if line.format_tokens[current_token_index].kind == .Eq { - append(&equal_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index], length = rhs_length}) - largest_rhs = max(largest_rhs, rhs_length) - break - } - } - - } - - //repeating myself, move to sub procedure - if p.config.align_style == .Align_On_Colon_And_Equals || !current_typed || current_not_mutable { - for colon_token in colon_tokens { - colon_token.format_token.spaces_before = largest_lhs - colon_token.length + 1 - } - } else if p.config.align_style == .Align_On_Type_And_Equals { - for type_token in type_tokens { - type_token.format_token.spaces_before = largest_lhs - type_token.length + 1 - } - } - - if current_typed { - for equal_token in equal_tokens { - equal_token.format_token.spaces_before = largest_rhs - equal_token.length + 1 - } - } else { - for equal_token in equal_tokens { - equal_token.format_token.spaces_before = 0 - } - } -} - -align_switch_stmt :: proc(p: ^Printer, index: int) { - switch_found := false - brace_token: Format_Token - brace_line: int - - found_switch_brace: for line, line_index in p.lines[index:] { - for format_token in line.format_tokens { - if format_token.kind == .Open_Brace && switch_found { - brace_token = format_token - brace_line = line_index + index - break found_switch_brace - } else if format_token.kind == .Open_Brace { - break - } else if format_token.kind == .Switch { - switch_found = true - } - } - } - - if !switch_found { - return - } - - largest := 0 - case_count := 0 - - TokenAndLength :: struct { - format_token: ^Format_Token, - length: int, - } - - format_tokens := make([dynamic]TokenAndLength, 0, brace_token.parameter_count, context.temp_allocator) - - //find all the switch cases that are one lined - for line in p.lines[brace_line + 1:] { - - case_found := false - colon_found := false - length := 0 - - for format_token, i in line.format_tokens { - - if format_token.kind == .Comment { - break - } - - //this will only happen if the case is one lined - if case_found && colon_found { - append(&format_tokens, TokenAndLength {format_token = &line.format_tokens[i], length = length}) - largest = max(length, largest) - break - } - - if format_token.kind == .Case { - case_found = true - case_count += 1 - } else if format_token.kind == .Colon { - colon_found = true - } - - length += len(format_token.text) + format_token.spaces_before - } - - if case_count >= brace_token.parameter_count { - break - } - } - - for token in format_tokens { - token.format_token.spaces_before = largest - token.length + 1 - } - -} - -align_enum :: proc(p: ^Printer, index: int) { - enum_found := false - brace_token: Format_Token - brace_line: int - - found_enum_brace: for line, line_index in p.lines[index:] { - for format_token in line.format_tokens { - if format_token.kind == .Open_Brace && enum_found { - brace_token = format_token - brace_line = line_index + index - break found_enum_brace - } else if format_token.kind == .Open_Brace { - break - } else if format_token.kind == .Enum { - enum_found = true - } - } - } - - if !enum_found { - return - } - - largest := 0 - comma_count := 0 - - TokenAndLength :: struct { - format_token: ^Format_Token, - length: int, - } - - format_tokens := make([dynamic]TokenAndLength, 0, brace_token.parameter_count, context.temp_allocator) - - for line in p.lines[brace_line + 1:] { - length := 0 - - for format_token, i in line.format_tokens { - if format_token.kind == .Comment { - break - } - - if format_token.kind == .Eq { - append(&format_tokens, TokenAndLength {format_token = &line.format_tokens[i], length = length}) - largest = max(length, largest) - break - } else if format_token.kind == .Comma { - comma_count += 1 - } - - length += len(format_token.text) + format_token.spaces_before - } - - if comma_count >= brace_token.parameter_count { - break - } - } - - for token in format_tokens { - token.format_token.spaces_before = largest - token.length + 1 - } - -} - -align_struct :: proc(p: ^Printer, index: int) -> int { - struct_found := false - brace_token: Format_Token - brace_line: int - - found_struct_brace: for line, line_index in p.lines[index:] { - for format_token in line.format_tokens { - if format_token.kind == .Open_Brace && struct_found { - brace_token = format_token - brace_line = line_index + index - break found_struct_brace - } else if format_token.kind == .Open_Brace { - break - } else if format_token.kind == .Struct { - struct_found = true - } - } - } - - if !struct_found { - return 0 - } - - largest := 0 - colon_count := 0 - nested := false - seen_brace := false - - TokenAndLength :: struct { - format_token: ^Format_Token, - length: int, - } - - format_tokens := make([]TokenAndLength, brace_token.parameter_count, context.temp_allocator) - - if brace_token.parameter_count == 0 { - return 0 - } - - end_line_index := 0 - - for line, line_index in p.lines[brace_line + 1:] { - length := 0 - - for format_token, i in line.format_tokens { - - //give up on nested structs - if format_token.kind == .Comment { - break - } else if format_token.kind == .Open_Paren { - break - } else if format_token.kind == .Open_Brace { - seen_brace = true - } else if format_token.kind == .Close_Brace { - seen_brace = false - } else if seen_brace { - continue - } - - if format_token.kind == .Colon { - format_tokens[colon_count] = {format_token = &line.format_tokens[i + 1], length = length} - - if format_tokens[colon_count].format_token.kind == .Struct { - nested = true - } - - colon_count += 1 - largest = max(length, largest) - } - - length += len(format_token.text) + format_token.spaces_before - } - - if nested { - end_line_index = line_index + brace_line + 1 - } - - if colon_count >= brace_token.parameter_count { - break - } - } - - //give up aligning nested, it never looks good - if nested { - for line, line_index in p.lines[end_line_index:] { - for format_token in line.format_tokens { - if format_token.kind == .Close_Brace { - return end_line_index + line_index - index - } - } - } - } - - for token in format_tokens { - token.format_token.spaces_before = largest - token.length + 1 - } - - return 0 -} - -align_comments :: proc(p: ^Printer) { - - Comment_Align_Info :: struct { - length: int, - begin: int, - end: int, - depth: int, - } - - comment_infos := make([dynamic]Comment_Align_Info, 0, context.temp_allocator) - - current_info: Comment_Align_Info - - for line, line_index in p.lines { - if len(line.format_tokens) <= 0 { - continue - } - - if .Line_Comment in line.types { - if current_info.end + 1 != line_index || current_info.depth != line.depth || - (current_info.begin == current_info.end && current_info.length == 0) { - - if (current_info.begin != 0 && current_info.end != 0) || current_info.length > 0 { - append(&comment_infos, current_info) - } - - current_info.begin = line_index - current_info.end = line_index - current_info.depth = line.depth - current_info.length = 0 - } - - length := 0 - - for format_token in line.format_tokens { - if format_token.kind == .Comment { - current_info.length = max(current_info.length, length) - current_info.end = line_index - } - - length += format_token.spaces_before + len(format_token.text) - } - } - } - - if (current_info.begin != 0 && current_info.end != 0) || current_info.length > 0 { - append(&comment_infos, current_info) - } - - for info in comment_infos { - - if info.begin == info.end || info.length == 0 { - continue - } - - for i := info.begin; i <= info.end; i += 1 { - l := p.lines[i] - - length := 0 - - for format_token in l.format_tokens { - if format_token.kind == .Comment { - if len(l.format_tokens) == 1 { - l.format_tokens[i].spaces_before = info.length + 1 - } else { - l.format_tokens[i].spaces_before = info.length - length + 1 - } - } - - length += format_token.spaces_before + len(format_token.text) - } - } - } -} diff --git a/core/odin/printer/visit.odin b/core/odin/printer/visit.odin deleted file mode 100644 index 571e4001d..000000000 --- a/core/odin/printer/visit.odin +++ /dev/null @@ -1,1629 +0,0 @@ -package odin_printer - -import "core:odin/ast" -import "core:odin/tokenizer" -import "core:strings" -import "core:fmt" -import "core:sort" - -//right now the attribute order is not linearly parsed(bug?) -@(private) -sort_attribute :: proc(s: ^[dynamic]^ast.Attribute) -> sort.Interface { - return sort.Interface { - collection = rawptr(s), - len = proc(it: sort.Interface) -> int { - s := (^[dynamic]^ast.Attribute)(it.collection) - return len(s^) - }, - less = proc(it: sort.Interface, i, j: int) -> bool { - s := (^[dynamic]^ast.Attribute)(it.collection) - return s[i].pos.offset < s[j].pos.offset - }, - swap = proc(it: sort.Interface, i, j: int) { - s := (^[dynamic]^ast.Attribute)(it.collection) - s[i], s[j] = s[j], s[i] - }, - } -} - -@(private) -comment_before_position :: proc(p: ^Printer, pos: tokenizer.Pos) -> bool { - if len(p.comments) <= p.latest_comment_index { - return false - } - - comment := p.comments[p.latest_comment_index] - - return comment.pos.offset < pos.offset -} - -@(private) -next_comment_group :: proc(p: ^Printer) { - p.latest_comment_index += 1 -} - -@(private) -push_comment :: proc(p: ^Printer, comment: tokenizer.Token) -> int { - if len(comment.text) == 0 { - return 0 - } - - if comment.text[:2] != "/*" { - format_token := Format_Token { - spaces_before = 1, - kind = .Comment, - text = comment.text, - } - - if len(p.current_line.format_tokens) == 0 { - format_token.spaces_before = 0 - } - - if !p.current_line.used { - p.current_line.used = true - p.current_line.depth = p.depth - } - - append(&p.current_line.format_tokens, format_token) - p.last_token = &p.current_line.format_tokens[len(p.current_line.format_tokens) - 1] - - hint_current_line(p, {.Line_Comment}) - - return 0 - } else { - builder := strings.builder_make(context.temp_allocator) - - c_len := len(comment.text) - trim_space := true - - multilines: [dynamic]string - - for i := 0; i < len(comment.text); i += 1 { - c := comment.text[i] - - if c != ' ' && c != '\t' { - trim_space = false - } - - switch { - case (c == ' ' || c == '\t' || c == '\n') && trim_space: - continue - case c == '\r' && comment.text[min(c_len - 1, i + 1)] == '\n': - append(&multilines, strings.to_string(builder)) - builder = strings.builder_make(context.temp_allocator) - trim_space = true - i += 1 - case c == '\n': - append(&multilines, strings.to_string(builder)) - builder = strings.builder_make(context.temp_allocator) - trim_space = true - case c == '/' && comment.text[min(c_len - 1, i + 1)] == '*': - strings.write_string(&builder, "/*") - trim_space = true - i += 1 - case c == '*' && comment.text[min(c_len - 1, i + 1)] == '/': - trim_space = true - strings.write_string(&builder, "*/") - i += 1 - case: - strings.write_byte(&builder, c) - } - } - - if strings.builder_len(builder) > 0 { - append(&multilines, strings.to_string(builder)) - } - - for line in multilines { - format_token := Format_Token { - spaces_before = 1, - kind = .Comment, - text = line, - } - - if len(p.current_line.format_tokens) == 0 { - format_token.spaces_before = 0 - } - - if strings.contains(line, "*/") { - unindent(p) - } - - if !p.current_line.used { - p.current_line.used = true - p.current_line.depth = p.depth - } - - append(&p.current_line.format_tokens, format_token) - p.last_token = &p.current_line.format_tokens[len(p.current_line.format_tokens) - 1] - - if strings.contains(line, "/*") { - indent(p) - } - - newline_position(p, 1) - } - - return len(multilines) - } -} - -@(private) -push_comments :: proc(p: ^Printer, pos: tokenizer.Pos) { - prev_comment: ^tokenizer.Token - prev_comment_lines: int - - for comment_before_position(p, pos) { - comment_group := p.comments[p.latest_comment_index] - - if prev_comment == nil { - lines := comment_group.pos.line - p.last_source_position.line - set_line(p, p.last_line_index + min(p.config.newline_limit+1, lines)) - } - - for comment, i in comment_group.list { - if prev_comment != nil && p.last_source_position.line != comment.pos.line { - newline_position(p, min(p.config.newline_limit+1, comment.pos.line - prev_comment.pos.line - prev_comment_lines)) - } - - prev_comment_lines = push_comment(p, comment) - prev_comment = &comment_group.list[i] - } - - next_comment_group(p) - } - - if prev_comment != nil { - newline_position(p, min(p.config.newline_limit+1, p.source_position.line - prev_comment.pos.line - prev_comment_lines)) - } -} - -@(private) -append_format_token :: proc(p: ^Printer, format_token: Format_Token) -> ^Format_Token { - format_token := format_token - - if p.last_token != nil && ( - p.last_token.kind == .Ellipsis || - p.last_token.kind == .Range_Half || p.last_token.kind == .Range_Full || - p.last_token.kind == .Open_Paren || p.last_token.kind == .Period || - p.last_token.kind == .Open_Brace || p.last_token.kind == .Open_Bracket) { - format_token.spaces_before = 0 - } else if p.merge_next_token { - format_token.spaces_before = 0 - p.merge_next_token = false - } else if p.space_next_token { - format_token.spaces_before = 1 - p.space_next_token = false - } - - push_comments(p, p.source_position) - - unwrapped_line := p.current_line - - if !unwrapped_line.used { - unwrapped_line.used = true - unwrapped_line.depth = p.depth - } - - if len(unwrapped_line.format_tokens) == 0 && format_token.spaces_before == 1 { - format_token.spaces_before = 0 - } - - p.last_source_position = p.source_position - p.last_line_index = p.current_line_index - - append(&unwrapped_line.format_tokens, format_token) - return &unwrapped_line.format_tokens[len(unwrapped_line.format_tokens) - 1] -} - -@(private) -push_format_token :: proc(p: ^Printer, format_token: Format_Token) { - p.last_token = append_format_token(p, format_token) -} - -@(private) -push_generic_token :: proc(p: ^Printer, kind: tokenizer.Token_Kind, spaces_before: int, value := "") { - format_token := Format_Token { - spaces_before = spaces_before, - kind = kind, - text = tokenizer.tokens[kind], - } - - if value != "" { - format_token.text = value - } - - p.last_token = append_format_token(p, format_token) -} - -@(private) -push_string_token :: proc(p: ^Printer, text: string, spaces_before: int) { - format_token := Format_Token { - spaces_before = spaces_before, - kind = .String, - text = text, - } - - p.last_token = append_format_token(p, format_token) -} - -@(private) -push_ident_token :: proc(p: ^Printer, text: string, spaces_before: int) { - format_token := Format_Token { - spaces_before = spaces_before, - kind = .Ident, - text = text, - } - - p.last_token = append_format_token(p, format_token) -} - -@(private) -set_source_position :: proc(p: ^Printer, pos: tokenizer.Pos) { - p.source_position = pos -} - -@(private) -move_line :: proc(p: ^Printer, pos: tokenizer.Pos) { - move_line_limit(p, pos, p.config.newline_limit+1) -} - -@(private) -move_line_limit :: proc(p: ^Printer, pos: tokenizer.Pos, limit: int) -> bool { - lines := min(pos.line - p.source_position.line, limit) - - if lines < 0 { - return false - } - - p.source_position = pos - p.current_line_index += lines - set_line(p, p.current_line_index) - return lines > 0 -} - -@(private) -set_line :: proc(p: ^Printer, line: int) -> ^Line { - unwrapped_line: ^Line - - if line >= len(p.lines) { - for i := len(p.lines); i <= line; i += 1 { - new_line: Line - new_line.format_tokens = make([dynamic]Format_Token, 0, 50, p.allocator) - append(&p.lines, new_line) - } - unwrapped_line = &p.lines[line] - } else { - unwrapped_line = &p.lines[line] - } - - p.current_line = unwrapped_line - p.current_line_index = line - - return unwrapped_line -} - -@(private) -newline_position :: proc(p: ^Printer, count: int) { - p.current_line_index += count - set_line(p, p.current_line_index) -} - -@(private) -indent :: proc(p: ^Printer) { - p.depth += 1 -} - -@(private) -unindent :: proc(p: ^Printer) { - p.depth -= 1 -} - -@(private) -merge_next_token :: proc(p: ^Printer) { - p.merge_next_token = true -} - -@(private) -space_next_token :: proc(p: ^Printer) { - p.space_next_token = true -} - -@(private) -hint_current_line :: proc(p: ^Printer, hint: Line_Type) { - p.current_line.types |= hint -} - -@(private) -visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) { - if decl == nil { - return - } - - #partial switch v in decl.derived_stmt { - case ^ast.Expr_Stmt: - move_line(p, decl.pos) - visit_expr(p, v.expr) - if p.config.semicolons { - push_generic_token(p, .Semicolon, 0) - } - case ^ast.When_Stmt: - visit_stmt(p, cast(^ast.Stmt)decl) - case ^ast.Foreign_Import_Decl: - if len(v.attributes) > 0 { - sort.sort(sort_attribute(&v.attributes)) - move_line(p, v.attributes[0].pos) - visit_attributes(p, v.attributes) - } - - move_line(p, decl.pos) - - push_generic_token(p, v.foreign_tok.kind, 0) - push_generic_token(p, v.import_tok.kind, 1) - - if v.name != nil { - push_ident_token(p, v.name.name, 1) - } - - for path in v.fullpaths { - push_ident_token(p, path, 0) - } - case ^ast.Foreign_Block_Decl: - if len(v.attributes) > 0 { - sort.sort(sort_attribute(&v.attributes)) - move_line(p, v.attributes[0].pos) - visit_attributes(p, v.attributes) - } - - move_line(p, decl.pos) - - push_generic_token(p, .Foreign, 0) - - visit_expr(p, v.foreign_library) - visit_stmt(p, v.body) - case ^ast.Import_Decl: - move_line(p, decl.pos) - - if v.name.text != "" { - push_generic_token(p, v.import_tok.kind, 1) - push_generic_token(p, v.name.kind, 1, v.name.text) - push_ident_token(p, v.fullpath, 1) - } else { - push_generic_token(p, v.import_tok.kind, 1) - push_ident_token(p, v.fullpath, 1) - } - - case ^ast.Value_Decl: - if len(v.attributes) > 0 { - sort.sort(sort_attribute(&v.attributes)) - move_line(p, v.attributes[0].pos) - visit_attributes(p, v.attributes) - } - - move_line(p, decl.pos) - - if v.is_using { - push_generic_token(p, .Using, 0) - } - - visit_exprs(p, v.names, {.Add_Comma}) - - hint_current_line(p, {.Value_Decl}) - - if v.type != nil { - if !v.is_mutable { - push_generic_token(p, .Colon, 0) - } else { - push_generic_token(p, .Colon, 0) - } - - visit_expr(p, v.type) - } else { - if !v.is_mutable { - push_generic_token(p, .Colon, 1) - push_generic_token(p, .Colon, 0) - } else { - push_generic_token(p, .Colon, 1) - } - } - - if v.is_mutable && v.type != nil && len(v.values) != 0 { - push_generic_token(p, .Eq, 1) - } else if v.is_mutable && v.type == nil && len(v.values) != 0 { - push_generic_token(p, .Eq, 0) - } else if !v.is_mutable && v.type != nil { - push_generic_token(p, .Colon, 0) - } - - if len(v.values) == 1 { - visit_expr(p, v.values[0]) //this is too ensure that one value are never newlined(procs, structs, etc.) - } else { - visit_exprs(p, v.values, {.Add_Comma}) - } - - add_semicolon := true - - for value in v.values { - #partial switch a in value.derived { - case ^ast.Union_Type, ^ast.Enum_Type, ^ast.Struct_Type, ^ast.Bit_Field_Type: - add_semicolon = false || called_in_stmt - case ^ast.Proc_Lit: - add_semicolon = false - } - } - - if add_semicolon && p.config.semicolons && !p.skip_semicolon { - push_generic_token(p, .Semicolon, 0) - } - - case: - panic(fmt.aprint(decl.derived)) - } -} - -@(private) -visit_exprs :: proc(p: ^Printer, list: []^ast.Expr, options := List_Options{}) { - if len(list) == 0 { - return - } - - // we have to newline the expressions to respect the source - for expr, i in list { - // Don't move the first expression, it looks bad - if i != 0 && .Enforce_Newline in options { - newline_position(p, 1) - } else if i != 0 { - move_line_limit(p, expr.pos, 1) - } - - visit_expr(p, expr, options) - - if (i != len(list) - 1 || .Trailing in options) && .Add_Comma in options { - push_generic_token(p, .Comma, 0) - } - } - - if len(list) > 1 && .Enforce_Newline in options { - newline_position(p, 1) - } -} - -@(private) -visit_bit_field_fields :: proc(p: ^Printer, list: []^ast.Bit_Field_Field, options := List_Options{}) { - if len(list) == 0 { - return - } - - // we have to newline the expressions to respect the source - for v, i in list { - // Don't move the first expression, it looks bad - if i != 0 && .Enforce_Newline in options { - newline_position(p, 1) - } else if i != 0 { - move_line_limit(p, v.pos, 1) - } - - visit_expr(p, v.name, options) - push_generic_token(p, .Colon, 0) - visit_expr(p, v.type, options) - push_generic_token(p, .Or, 1) - visit_expr(p, v.bit_size, options) - - if (i != len(list) - 1 || .Trailing in options) && .Add_Comma in options { - push_generic_token(p, .Comma, 0) - } - } - - if len(list) > 1 && .Enforce_Newline in options { - newline_position(p, 1) - } -} - -@(private) -visit_attributes :: proc(p: ^Printer, attributes: [dynamic]^ast.Attribute) { - if len(attributes) == 0 { - return - } - - for attribute in attributes { - move_line_limit(p, attribute.pos, 1) - - push_generic_token(p, .At, 0) - push_generic_token(p, .Open_Paren, 0) - - visit_exprs(p, attribute.elems, {.Add_Comma}) - - push_generic_token(p, .Close_Paren, 0) - } -} - -@(private) -visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Generic, empty_block := false, block_stmt := false) { - if stmt == nil { - return - } - - - switch v in stmt.derived_stmt { - case ^ast.Bad_Stmt: - case ^ast.Bad_Decl: - case ^ast.Package_Decl: - - case ^ast.Empty_Stmt: - push_generic_token(p, .Semicolon, 0) - case ^ast.Tag_Stmt: - push_generic_token(p, .Hash, 1) - push_generic_token(p, v.op.kind, 1, v.op.text) - visit_stmt(p, v.stmt) - - - case ^ast.Import_Decl: - visit_decl(p, cast(^ast.Decl)stmt, true) - return - case ^ast.Value_Decl: - visit_decl(p, cast(^ast.Decl)stmt, true) - return - case ^ast.Foreign_Import_Decl: - visit_decl(p, cast(^ast.Decl)stmt, true) - return - case ^ast.Foreign_Block_Decl: - visit_decl(p, cast(^ast.Decl)stmt, true) - return - - case ^ast.Using_Stmt: - move_line(p, v.pos) - - push_generic_token(p, .Using, 1) - - visit_exprs(p, v.list, {.Add_Comma}) - - if p.config.semicolons { - push_generic_token(p, .Semicolon, 0) - } - case ^ast.Block_Stmt: - move_line(p, v.pos) - - if v.pos.line == v.end.line { - if !empty_block { - push_generic_token(p, .Open_Brace, 1) - } - - set_source_position(p, v.pos) - - visit_block_stmts(p, v.stmts, len(v.stmts) > 1 && p.config.split_multiple_stmts) - - set_source_position(p, v.end) - - if !empty_block { - push_generic_token(p, .Close_Brace, 0) - } - } else { - if !empty_block { - visit_begin_brace(p, v.pos, block_type, len(v.stmts)) - } - - set_source_position(p, v.pos) - - visit_block_stmts(p, v.stmts, len(v.stmts) > 1 && p.config.split_multiple_stmts) - - if !empty_block { - visit_end_brace(p, v.end) - } - } - case ^ast.If_Stmt: - move_line(p, v.pos) - - if v.label != nil { - visit_expr(p, v.label) - push_generic_token(p, .Colon, 0) - } - - push_generic_token(p, .If, 1) - - hint_current_line(p, {.If}) - - if v.init != nil { - p.skip_semicolon = true - visit_stmt(p, v.init) - p.skip_semicolon = false - push_generic_token(p, .Semicolon, 0) - } - - visit_expr(p, v.cond) - - uses_do := false - - if check_stmt, ok := v.body.derived.(^ast.Block_Stmt); ok && check_stmt.uses_do { - uses_do = true - } - - if uses_do && !p.config.convert_do { - push_generic_token(p, .Do, 1) - visit_stmt(p, v.body, .If_Stmt, true) - } else { - if uses_do { - newline_position(p, 1) - } - - set_source_position(p, v.body.pos) - - visit_stmt(p, v.body, .If_Stmt) - - set_source_position(p, v.body.end) - } - - if v.else_stmt != nil { - - if p.config.brace_style == .Allman || p.config.brace_style == .Stroustrup { - newline_position(p, 1) - } - - push_generic_token(p, .Else, 1) - - set_source_position(p, v.else_stmt.pos) - - visit_stmt(p, v.else_stmt) - } - case ^ast.Switch_Stmt: - move_line(p, v.pos) - - if v.label != nil { - visit_expr(p, v.label) - push_generic_token(p, .Colon, 0) - } - - if v.partial { - push_ident_token(p, "#partial", 1) - } - - push_generic_token(p, .Switch, 1) - - hint_current_line(p, {.Switch_Stmt}) - - if v.init != nil { - p.skip_semicolon = true - visit_stmt(p, v.init) - p.skip_semicolon = false - } - - if v.init != nil && v.cond != nil { - push_generic_token(p, .Semicolon, 0) - } - - visit_expr(p, v.cond) - visit_stmt(p, v.body) - case ^ast.Case_Clause: - move_line(p, v.pos) - - if !p.config.indent_cases { - unindent(p) - } - - push_generic_token(p, .Case, 0) - - if v.list != nil { - visit_exprs(p, v.list, {.Add_Comma}) - } - - push_generic_token(p, v.terminator.kind, 0) - - indent(p) - - visit_block_stmts(p, v.body) - - unindent(p) - - if !p.config.indent_cases { - indent(p) - } - case ^ast.Type_Switch_Stmt: - move_line(p, v.pos) - - hint_current_line(p, {.Switch_Stmt}) - - if v.label != nil { - visit_expr(p, v.label) - push_generic_token(p, .Colon, 0) - } - - if v.partial { - push_ident_token(p, "#partial", 1) - } - - push_generic_token(p, .Switch, 1) - - visit_stmt(p, v.tag) - visit_stmt(p, v.body) - case ^ast.Assign_Stmt: - move_line(p, v.pos) - - hint_current_line(p, {.Assign}) - - visit_exprs(p, v.lhs, {.Add_Comma}) - - push_generic_token(p, v.op.kind, 1) - - visit_exprs(p, v.rhs, {.Add_Comma}) - - if block_stmt && p.config.semicolons { - push_generic_token(p, .Semicolon, 0) - } - case ^ast.Expr_Stmt: - move_line(p, v.pos) - visit_expr(p, v.expr) - if block_stmt && p.config.semicolons { - push_generic_token(p, .Semicolon, 0) - } - case ^ast.For_Stmt: - // this should be simplified - move_line(p, v.pos) - - if v.label != nil { - visit_expr(p, v.label) - push_generic_token(p, .Colon, 0) - } - - push_generic_token(p, .For, 1) - - hint_current_line(p, {.For}) - - if v.init != nil { - p.skip_semicolon = true - visit_stmt(p, v.init) - p.skip_semicolon = false - push_generic_token(p, .Semicolon, 0) - } else if v.post != nil { - push_generic_token(p, .Semicolon, 0) - } - - if v.cond != nil { - move_line(p, v.cond.pos) - visit_expr(p, v.cond) - } - - if v.post != nil { - push_generic_token(p, .Semicolon, 0) - move_line(p, v.post.pos) - visit_stmt(p, v.post) - } else if v.post == nil && v.cond != nil && v.init != nil { - push_generic_token(p, .Semicolon, 0) - } - - visit_stmt(p, v.body) - - case ^ast.Inline_Range_Stmt: - move_line(p, v.pos) - - if v.label != nil { - visit_expr(p, v.label) - push_generic_token(p, .Colon, 0) - } - - push_ident_token(p, "#unroll", 0) - - push_generic_token(p, .For, 1) - - hint_current_line(p, {.For}) - - visit_expr(p, v.val0) - - if v.val1 != nil { - push_generic_token(p, .Comma, 0) - visit_expr(p, v.val1) - } - - push_generic_token(p, .In, 1) - - visit_expr(p, v.expr) - visit_stmt(p, v.body) - - case ^ast.Range_Stmt: - move_line(p, v.pos) - - if v.label != nil { - visit_expr(p, v.label) - push_generic_token(p, .Colon, 0) - } - - push_generic_token(p, .For, 1) - - hint_current_line(p, {.For}) - - if len(v.vals) >= 1 { - visit_expr(p, v.vals[0]) - } - - if len(v.vals) >= 2 { - push_generic_token(p, .Comma, 0) - visit_expr(p, v.vals[1]) - } - - push_generic_token(p, .In, 1) - - visit_expr(p, v.expr) - - visit_stmt(p, v.body) - case ^ast.Return_Stmt: - move_line(p, v.pos) - - push_generic_token(p, .Return, 1) - - if v.results != nil { - visit_exprs(p, v.results, {.Add_Comma}) - } - - if block_stmt && p.config.semicolons { - push_generic_token(p, .Semicolon, 0) - } - case ^ast.Defer_Stmt: - move_line(p, v.pos) - push_generic_token(p, .Defer, 0) - - visit_stmt(p, v.stmt) - - if p.config.semicolons { - push_generic_token(p, .Semicolon, 0) - } - case ^ast.When_Stmt: - move_line(p, v.pos) - push_generic_token(p, .When, 1) - visit_expr(p, v.cond) - - visit_stmt(p, v.body) - - if v.else_stmt != nil { - - if p.config.brace_style == .Allman { - newline_position(p, 1) - } - - push_generic_token(p, .Else, 1) - - set_source_position(p, v.else_stmt.pos) - - visit_stmt(p, v.else_stmt) - } - - case ^ast.Branch_Stmt: - move_line(p, v.pos) - - push_generic_token(p, v.tok.kind, 0) - - if v.label != nil { - visit_expr(p, v.label) - } - - if p.config.semicolons { - push_generic_token(p, .Semicolon, 0) - } - case: - panic(fmt.aprint(stmt.derived)) - } - - set_source_position(p, stmt.end) -} - -@(private) -push_where_clauses :: proc(p: ^Printer, clauses: []^ast.Expr) { - if len(clauses) == 0 { - return - } - - // TODO(bill): This is not outputting correctly at all - - move_line(p, clauses[0].pos) - push_generic_token(p, .Where, 1) - - force_newline := false - - for expr, i in clauses { - // Don't move the first expression, it looks bad - if i != 0 && i != len(clauses)-1 && force_newline { - newline_position(p, 1) - } else if i != 0 { - move_line_limit(p, expr.pos, 1) - } - - visit_expr(p, expr) - - if i != len(clauses) - 1 { - push_generic_token(p, .Comma, 0) - } - } - - if len(clauses) > 1 && force_newline { - newline_position(p, 1) - } -} - -@(private) -push_poly_params :: proc(p: ^Printer, poly_params: ^ast.Field_List) { - if poly_params != nil { - push_generic_token(p, .Open_Paren, 0) - visit_field_list(p, poly_params, {.Add_Comma, .Enforce_Poly_Names}) - push_generic_token(p, .Close_Paren, 0) - } -} - - -@(private) -visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) { - if expr == nil { - return - } - - set_source_position(p, expr.pos) - - switch v in expr.derived_expr { - case ^ast.Bad_Expr: - - case ^ast.Tag_Expr: - push_generic_token(p, .Hash, 1) - push_generic_token(p, v.op.kind, 1, v.op.text) - visit_expr(p, v.expr) - - case ^ast.Inline_Asm_Expr: - push_generic_token(p, v.tok.kind, 1, v.tok.text) - - push_generic_token(p, .Open_Paren, 1) - visit_exprs(p, v.param_types, {.Add_Comma}) - push_generic_token(p, .Close_Paren, 0) - - push_generic_token(p, .Sub, 1) - push_generic_token(p, .Gt, 0) - - visit_expr(p, v.return_type) - - push_generic_token(p, .Open_Brace, 1) - visit_expr(p, v.asm_string) - push_generic_token(p, .Comma, 0) - visit_expr(p, v.constraints_string) - push_generic_token(p, .Close_Brace, 0) - case ^ast.Undef: - push_generic_token(p, .Undef, 1) - case ^ast.Auto_Cast: - push_generic_token(p, v.op.kind, 1) - visit_expr(p, v.expr) - case ^ast.Ternary_If_Expr: - visit_expr(p, v.x) - push_generic_token(p, v.op1.kind, 1) - visit_expr(p, v.cond) - push_generic_token(p, v.op2.kind, 1) - visit_expr(p, v.y) - case ^ast.Ternary_When_Expr: - visit_expr(p, v.x) - push_generic_token(p, v.op1.kind, 1) - visit_expr(p, v.cond) - push_generic_token(p, v.op2.kind, 1) - visit_expr(p, v.y) - case ^ast.Or_Else_Expr: - visit_expr(p, v.x) - push_generic_token(p, v.token.kind, 1) - visit_expr(p, v.y) - case ^ast.Or_Return_Expr: - visit_expr(p, v.expr) - push_generic_token(p, v.token.kind, 1) - case ^ast.Or_Branch_Expr: - visit_expr(p, v.expr) - push_generic_token(p, v.token.kind, 1) - if v.label != nil { - visit_expr(p, v.label) - } - - case ^ast.Selector_Call_Expr: - visit_expr(p, v.call.expr) - push_generic_token(p, .Open_Paren, 1) - visit_exprs(p, v.call.args, {.Add_Comma}) - push_generic_token(p, .Close_Paren, 0) - case ^ast.Ellipsis: - push_generic_token(p, .Ellipsis, 1) - visit_expr(p, v.expr) - case ^ast.Relative_Type: - visit_expr(p, v.tag) - visit_expr(p, v.type) - case ^ast.Slice_Expr: - visit_expr(p, v.expr) - push_generic_token(p, .Open_Bracket, 0) - visit_expr(p, v.low) - push_generic_token(p, v.interval.kind, 0) - if v.high != nil { - merge_next_token(p) - visit_expr(p, v.high) - } - push_generic_token(p, .Close_Bracket, 0) - case ^ast.Ident: - if .Enforce_Poly_Names in options { - push_generic_token(p, .Dollar, 1) - push_ident_token(p, v.name, 0) - } else { - push_ident_token(p, v.name, 1) - } - case ^ast.Deref_Expr: - visit_expr(p, v.expr) - push_generic_token(p, v.op.kind, 0) - case ^ast.Type_Cast: - push_generic_token(p, v.tok.kind, 1) - push_generic_token(p, .Open_Paren, 0) - visit_expr(p, v.type) - push_generic_token(p, .Close_Paren, 0) - merge_next_token(p) - visit_expr(p, v.expr) - case ^ast.Basic_Directive: - push_generic_token(p, v.tok.kind, 1) - push_ident_token(p, v.name, 0) - case ^ast.Distinct_Type: - push_generic_token(p, .Distinct, 1) - visit_expr(p, v.type) - case ^ast.Dynamic_Array_Type: - visit_expr(p, v.tag) - push_generic_token(p, .Open_Bracket, 1) - push_generic_token(p, .Dynamic, 0) - push_generic_token(p, .Close_Bracket, 0) - merge_next_token(p) - visit_expr(p, v.elem) - case ^ast.Bit_Set_Type: - push_generic_token(p, .Bit_Set, 1) - push_generic_token(p, .Open_Bracket, 0) - - visit_expr(p, v.elem) - - if v.underlying != nil { - push_generic_token(p, .Semicolon, 0) - visit_expr(p, v.underlying) - } - - push_generic_token(p, .Close_Bracket, 0) - case ^ast.Union_Type: - push_generic_token(p, .Union, 1) - - push_poly_params(p, v.poly_params) - - switch v.kind { - case .Normal: - case .maybe: push_ident_token(p, "#maybe", 1) - case .no_nil: push_ident_token(p, "#no_nil", 1) - case .shared_nil: push_ident_token(p, "#shared_nil", 1) - } - - push_where_clauses(p, v.where_clauses) - - if v.variants != nil && (len(v.variants) == 0 || v.pos.line == v.end.line) { - push_generic_token(p, .Open_Brace, 1) - visit_exprs(p, v.variants, {.Add_Comma}) - push_generic_token(p, .Close_Brace, 0) - } else { - visit_begin_brace(p, v.pos, .Generic) - newline_position(p, 1) - set_source_position(p, v.variants[0].pos) - visit_exprs(p, v.variants, {.Add_Comma, .Trailing}) - visit_end_brace(p, v.end) - } - case ^ast.Enum_Type: - push_generic_token(p, .Enum, 1) - - hint_current_line(p, {.Enum}) - - if v.base_type != nil { - visit_expr(p, v.base_type) - } - - if v.fields != nil && (len(v.fields) == 0 || v.pos.line == v.end.line) { - push_generic_token(p, .Open_Brace, 1) - visit_exprs(p, v.fields, {.Add_Comma}) - push_generic_token(p, .Close_Brace, 0) - } else { - visit_begin_brace(p, v.pos, .Generic, len(v.fields)) - newline_position(p, 1) - set_source_position(p, v.fields[0].pos) - visit_exprs(p, v.fields, {.Add_Comma, .Trailing, .Enforce_Newline}) - set_source_position(p, v.end) - visit_end_brace(p, v.end) - } - - set_source_position(p, v.end) - case ^ast.Struct_Type: - push_generic_token(p, .Struct, 1) - - hint_current_line(p, {.Struct}) - - push_poly_params(p, v.poly_params) - - if v.is_packed { - push_ident_token(p, "#packed", 1) - } - - if v.is_raw_union { - push_ident_token(p, "#raw_union", 1) - } - - if v.align != nil { - push_ident_token(p, "#align", 1) - visit_expr(p, v.align) - } - - push_where_clauses(p, v.where_clauses) - - if v.fields != nil && (len(v.fields.list) == 0 || v.pos.line == v.end.line) { - push_generic_token(p, .Open_Brace, 1) - set_source_position(p, v.fields.pos) - visit_field_list(p, v.fields, {.Add_Comma}) - push_generic_token(p, .Close_Brace, 0) - } else if v.fields != nil { - visit_begin_brace(p, v.pos, .Generic, len(v.fields.list)) - set_source_position(p, v.fields.pos) - visit_field_list(p, v.fields, {.Add_Comma, .Trailing, .Enforce_Newline}) - visit_end_brace(p, v.end) - } - - set_source_position(p, v.end) - case ^ast.Proc_Lit: - switch v.inlining { - case .None: - case .Inline: - push_ident_token(p, "#force_inline", 0) - case .No_Inline: - push_ident_token(p, "#force_no_inline", 0) - } - - visit_proc_type(p, v.type, true) - - push_where_clauses(p, v.where_clauses) - - if v.body != nil { - set_source_position(p, v.body.pos) - visit_stmt(p, v.body, .Proc) - } else { - push_generic_token(p, .Undef, 1) - } - case ^ast.Proc_Type: - visit_proc_type(p, v) - case ^ast.Basic_Lit: - push_generic_token(p, v.tok.kind, 1, v.tok.text) - case ^ast.Binary_Expr: - visit_binary_expr(p, v) - case ^ast.Implicit_Selector_Expr: - push_generic_token(p, .Period, 1) - push_ident_token(p, v.field.name, 0) - case ^ast.Call_Expr: - visit_expr(p, v.expr) - - push_format_token(p, - Format_Token { - kind = .Open_Paren, - type = .Call, - text = "(", - }, - ) - - hint_current_line(p, {.Call}) - - visit_call_exprs(p, v.args, v.ellipsis.kind == .Ellipsis) - push_generic_token(p, .Close_Paren, 0) - case ^ast.Typeid_Type: - push_generic_token(p, .Typeid, 1) - - if v.specialization != nil { - push_generic_token(p, .Quo, 0) - visit_expr(p, v.specialization) - } - case ^ast.Selector_Expr: - visit_expr(p, v.expr) - push_generic_token(p, v.op.kind, 0) - visit_expr(p, v.field) - case ^ast.Paren_Expr: - push_generic_token(p, .Open_Paren, 1) - visit_expr(p, v.expr) - push_generic_token(p, .Close_Paren, 0) - case ^ast.Index_Expr: - visit_expr(p, v.expr) - push_generic_token(p, .Open_Bracket, 0) - visit_expr(p, v.index) - push_generic_token(p, .Close_Bracket, 0) - case ^ast.Matrix_Index_Expr: - visit_expr(p, v.expr) - push_generic_token(p, .Open_Bracket, 0) - visit_expr(p, v.row_index) - push_generic_token(p, .Comma, 0) - visit_expr(p, v.column_index) - push_generic_token(p, .Close_Bracket, 0) - case ^ast.Proc_Group: - push_generic_token(p, v.tok.kind, 1) - - if len(v.args) != 0 && v.pos.line != v.args[len(v.args) - 1].pos.line { - visit_begin_brace(p, v.pos, .Generic) - newline_position(p, 1) - set_source_position(p, v.args[0].pos) - visit_exprs(p, v.args, {.Add_Comma, .Trailing}) - visit_end_brace(p, v.end) - } else { - push_generic_token(p, .Open_Brace, 0) - visit_exprs(p, v.args, {.Add_Comma}) - push_generic_token(p, .Close_Brace, 0) - } - - case ^ast.Comp_Lit: - if v.type != nil { - visit_expr(p, v.type) - } - - if len(v.elems) != 0 && v.pos.line != v.elems[len(v.elems) - 1].pos.line { - visit_begin_brace(p, v.pos, .Comp_Lit, 0) - newline_position(p, 1) - set_source_position(p, v.elems[0].pos) - visit_exprs(p, v.elems, {.Add_Comma, .Trailing}) - visit_end_brace(p, v.end) - } else { - push_generic_token(p, .Open_Brace, 0 if v.type != nil else 1) - visit_exprs(p, v.elems, {.Add_Comma}) - push_generic_token(p, .Close_Brace, 0) - } - - case ^ast.Unary_Expr: - push_generic_token(p, v.op.kind, 1) - merge_next_token(p) - visit_expr(p, v.expr) - case ^ast.Field_Value: - visit_expr(p, v.field) - push_generic_token(p, .Eq, 1) - visit_expr(p, v.value) - case ^ast.Type_Assertion: - visit_expr(p, v.expr) - - if unary, ok := v.type.derived.(^ast.Unary_Expr); ok && unary.op.text == "?" { - push_generic_token(p, .Period, 0) - visit_expr(p, v.type) - } else { - push_generic_token(p, .Period, 0) - push_generic_token(p, .Open_Paren, 0) - visit_expr(p, v.type) - push_generic_token(p, .Close_Paren, 0) - } - - case ^ast.Pointer_Type: - push_generic_token(p, .Pointer, 1) - merge_next_token(p) - visit_expr(p, v.elem) - case ^ast.Implicit: - push_generic_token(p, v.tok.kind, 1) - case ^ast.Poly_Type: - push_generic_token(p, .Dollar, 1) - merge_next_token(p) - visit_expr(p, v.type) - - if v.specialization != nil { - push_generic_token(p, .Quo, 0) - merge_next_token(p) - visit_expr(p, v.specialization) - } - case ^ast.Array_Type: - visit_expr(p, v.tag) - push_generic_token(p, .Open_Bracket, 1) - visit_expr(p, v.len) - push_generic_token(p, .Close_Bracket, 0) - merge_next_token(p) - visit_expr(p, v.elem) - case ^ast.Map_Type: - push_generic_token(p, .Map, 1) - push_generic_token(p, .Open_Bracket, 0) - visit_expr(p, v.key) - push_generic_token(p, .Close_Bracket, 0) - merge_next_token(p) - visit_expr(p, v.value) - case ^ast.Helper_Type: - visit_expr(p, v.type) - case ^ast.Multi_Pointer_Type: - push_generic_token(p, .Open_Bracket, 1) - push_generic_token(p, .Pointer, 0) - push_generic_token(p, .Close_Bracket, 0) - visit_expr(p, v.elem) - case ^ast.Matrix_Type: - push_generic_token(p, .Matrix, 1) - push_generic_token(p, .Open_Bracket, 0) - visit_expr(p, v.row_count) - push_generic_token(p, .Comma, 0) - visit_expr(p, v.column_count) - push_generic_token(p, .Close_Bracket, 0) - visit_expr(p, v.elem) - case ^ast.Bit_Field_Type: - push_generic_token(p, .Bit_Field, 1) - - visit_expr(p, v.backing_type) - - if len(v.fields) == 0 || v.pos.line == v.close.line { - push_generic_token(p, .Open_Brace, 1) - visit_bit_field_fields(p, v.fields, {.Add_Comma}) - push_generic_token(p, .Close_Brace, 0) - } else { - visit_begin_brace(p, v.pos, .Generic, len(v.fields)) - newline_position(p, 1) - set_source_position(p, v.fields[0].pos) - visit_bit_field_fields(p, v.fields, {.Add_Comma, .Trailing, .Enforce_Newline}) - set_source_position(p, v.close) - visit_end_brace(p, v.close) - } - - set_source_position(p, v.close) - case: - panic(fmt.aprint(expr.derived)) - } -} - -visit_begin_brace :: proc(p: ^Printer, begin: tokenizer.Pos, type: Block_Type, count := 0, same_line_spaces_before := 1) { - set_source_position(p, begin) - - newline_braced := p.config.brace_style == .Allman - newline_braced |= p.config.brace_style == .K_And_R && type == .Proc - newline_braced &= p.config.brace_style != ._1TBS - - format_token := Format_Token { - kind = .Open_Brace, - parameter_count = count, - text = "{", - } - - if newline_braced { - newline_position(p, 1) - push_format_token(p, format_token) - indent(p) - } else { - format_token.spaces_before = same_line_spaces_before - push_format_token(p, format_token) - indent(p) - } -} - -visit_end_brace :: proc(p: ^Printer, end: tokenizer.Pos) { - move_line(p, end) - push_generic_token(p, .Close_Brace, 0) - unindent(p) - p.current_line.depth = p.depth -} - -visit_block_stmts :: proc(p: ^Printer, stmts: []^ast.Stmt, split := false) { - for stmt, i in stmts { - visit_stmt(p, stmt, .Generic, false, true) - - if split && i != len(stmts) - 1 && stmt.pos.line == stmts[i + 1].pos.line { - newline_position(p, 1) - } - } -} - -List_Option :: enum u8 { - Add_Comma, - Trailing, - Enforce_Newline, - Enforce_Poly_Names, -} - -List_Options :: distinct bit_set[List_Option] - -visit_field_list :: proc(p: ^Printer, list: ^ast.Field_List, options := List_Options{}) { - if list.list == nil { - return - } - - for field, i in list.list { - if !move_line_limit(p, field.pos, 1) && .Enforce_Newline in options { - newline_position(p, 1) - } - - if .Using in field.flags { - push_generic_token(p, .Using, 1) - } - - name_options := List_Options{.Add_Comma} - if .Enforce_Poly_Names in options { - name_options += {.Enforce_Poly_Names} - } - - visit_exprs(p, field.names, name_options) - - if field.type != nil { - if len(field.names) != 0 { - push_generic_token(p, .Colon, 0) - } - visit_expr(p, field.type) - } else { - push_generic_token(p, .Colon, 1) - push_generic_token(p, .Eq, 0) - visit_expr(p, field.default_value) - } - - if field.tag.text != "" { - push_generic_token(p, field.tag.kind, 1, field.tag.text) - } - - if (i != len(list.list) - 1 || .Trailing in options) && .Add_Comma in options { - push_generic_token(p, .Comma, 0) - } - } -} - -visit_proc_type :: proc(p: ^Printer, proc_type: ^ast.Proc_Type, is_proc_lit := false) { - if is_proc_lit { - push_format_token(p, Format_Token { - kind = .Proc, - type = .Proc_Lit, - text = "proc", - spaces_before = 1, - }) - } else { - push_format_token(p, Format_Token { - kind = .Proc, - text = "proc", - spaces_before = 1, - }) - } - - explicit_calling := false - - if v, ok := proc_type.calling_convention.(string); ok { - explicit_calling = true - push_string_token(p, v, 1) - } - - if explicit_calling { - push_generic_token(p, .Open_Paren, 1) - } else { - push_generic_token(p, .Open_Paren, 0) - } - - visit_signature_list(p, proc_type.params, false) - - push_generic_token(p, .Close_Paren, 0) - - if proc_type.results != nil { - push_generic_token(p, .Sub, 1) - push_generic_token(p, .Gt, 0) - - use_parens := false - - if len(proc_type.results.list) > 1 { - use_parens = true - } else if len(proc_type.results.list) == 1 { - - for name in proc_type.results.list[0].names { - if ident, ok := name.derived.(^ast.Ident); ok { - if ident.name != "_" { - use_parens = true - } - } - } - } - - if use_parens { - push_generic_token(p, .Open_Paren, 1) - visit_signature_list(p, proc_type.results) - push_generic_token(p, .Close_Paren, 0) - } else { - visit_signature_list(p, proc_type.results) - } - } -} - -visit_binary_expr :: proc(p: ^Printer, binary: ^ast.Binary_Expr) { - move_line(p, binary.left.pos) - - if v, ok := binary.left.derived.(^ast.Binary_Expr); ok { - visit_binary_expr(p, v) - } else { - visit_expr(p, binary.left) - } - - either_implicit_selector := false - if _, lok := binary.left.derived.(^ast.Implicit_Selector_Expr); lok { - either_implicit_selector = true - } else if _, rok := binary.right.derived.(^ast.Implicit_Selector_Expr); rok { - either_implicit_selector = true - } - - #partial switch binary.op.kind { - case .Ellipsis: - push_generic_token(p, binary.op.kind, 1 if either_implicit_selector else 0, - tokenizer.tokens[tokenizer.Token_Kind.Range_Full]) - case .Range_Half, .Range_Full: - push_generic_token(p, binary.op.kind, 1 if either_implicit_selector else 0) - case: - push_generic_token(p, binary.op.kind, 1) - } - - move_line(p, binary.right.pos) - - - if v, ok := binary.right.derived.(^ast.Binary_Expr); ok { - visit_binary_expr(p, v) - } else { - visit_expr(p, binary.right) - } -} - -visit_call_exprs :: proc(p: ^Printer, list: []^ast.Expr, ellipsis := false) { - if len(list) == 0 { - return - } - - // all the expression are on the line - if list[0].pos.line == list[len(list) - 1].pos.line { - for expr, i in list { - if i == len(list) - 1 && ellipsis { - push_generic_token(p, .Ellipsis, 0) - } - - visit_expr(p, expr) - - if i != len(list) - 1 { - push_generic_token(p, .Comma, 0) - } - } - } else { - for expr, i in list { - // we have to newline the expressions to respect the source - move_line_limit(p, expr.pos, 1) - - if i == len(list) - 1 && ellipsis { - push_generic_token(p, .Ellipsis, 0) - } - - visit_expr(p, expr) - - if i != len(list) - 1 { - push_generic_token(p, .Comma, 0) - } - } - } -} - -visit_signature_list :: proc(p: ^Printer, list: ^ast.Field_List, remove_blank := true) { - if list.list == nil { - return - } - - for field, i in list.list { - if i != 0 { - move_line_limit(p, field.pos, 1) - } - - if .Using in field.flags { - push_generic_token(p, .Using, 0) - } - - named := false - - for name in field.names { - if ident, ok := name.derived.(^ast.Ident); ok { - //for some reason the parser uses _ to mean empty - if ident.name != "_" || !remove_blank { - named = true - } - } else { - //alternative is poly names - named = true - } - } - - if named { - visit_exprs(p, field.names, {.Add_Comma}) - - if len(field.names) != 0 && field.type != nil { - push_generic_token(p, .Colon, 0) - } - } - - if field.type != nil && field.default_value != nil { - visit_expr(p, field.type) - push_generic_token(p, .Eq, 1) - visit_expr(p, field.default_value) - } else if field.type != nil { - visit_expr(p, field.type) - } else { - push_generic_token(p, .Colon, 1) - push_generic_token(p, .Eq, 0) - visit_expr(p, field.default_value) - } - - if i != len(list.list) - 1 { - push_generic_token(p, .Comma, 0) - } - } -} diff --git a/core/os/dir_freebsd.odin b/core/os/dir_bsd.odin similarity index 98% rename from core/os/dir_freebsd.odin rename to core/os/dir_bsd.odin index 2965182cd..c0dc8ad1f 100644 --- a/core/os/dir_freebsd.odin +++ b/core/os/dir_bsd.odin @@ -1,3 +1,4 @@ +//+build freebsd, netbsd package os import "core:mem" diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index 491507313..9ca78948e 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -87,8 +87,12 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F find_data := &win32.WIN32_FIND_DATAW{} find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data) + if find_handle == win32.INVALID_HANDLE_VALUE { + err = Errno(win32.GetLastError()) + return dfi[:], err + } defer win32.FindClose(find_handle) - for n != 0 && find_handle != nil { + for n != 0 { fi: File_Info fi = find_data_to_file_info(path, find_data) if fi.name != "" { diff --git a/core/os/file_windows.odin b/core/os/file_windows.odin index 3efe30d17..d900c5e70 100644 --- a/core/os/file_windows.odin +++ b/core/os/file_windows.odin @@ -125,7 +125,7 @@ read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) { src := buf8[:buf8_len] ctrl_z := false - for i := 0; i < len(src) && n+i < len(b); i += 1 { + for i := 0; i < len(src) && n < len(b); i += 1 { x := src[i] if x == 0x1a { // ctrl-z ctrl_z = true diff --git a/core/os/os.odin b/core/os/os.odin index aa460fe01..51652a52b 100644 --- a/core/os/os.odin +++ b/core/os/os.odin @@ -111,7 +111,7 @@ read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator, length: i64 err: Errno if length, err = file_size(fd); err != 0 { - return nil, false + return nil, false } if length <= 0 { @@ -120,7 +120,7 @@ read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator, data = make([]byte, int(length), allocator, loc) if data == nil { - return nil, false + return nil, false } bytes_read, read_err := read_full(fd, data) diff --git a/core/os/os2/allocators.odin b/core/os/os2/allocators.odin new file mode 100644 index 000000000..40672face --- /dev/null +++ b/core/os/os2/allocators.odin @@ -0,0 +1,48 @@ +//+private +package os2 + +import "base:runtime" + +@(require_results) +file_allocator :: proc() -> runtime.Allocator { + return heap_allocator() +} + +temp_allocator_proc :: runtime.arena_allocator_proc + +@(private="file", thread_local) +global_default_temp_allocator_arena: runtime.Arena + +@(require_results) +temp_allocator :: proc() -> runtime.Allocator { + return runtime.Allocator{ + procedure = temp_allocator_proc, + data = &global_default_temp_allocator_arena, + } +} + +@(require_results) +temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) { + temp = runtime.arena_temp_begin(&global_default_temp_allocator_arena, loc) + return +} + +temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_location) { + runtime.arena_temp_end(temp, loc) +} + +@(fini, private) +temp_allocator_fini :: proc() { + runtime.arena_destroy(&global_default_temp_allocator_arena) + global_default_temp_allocator_arena = {} +} + +@(deferred_out=temp_allocator_temp_end) +TEMP_ALLOCATOR_GUARD :: #force_inline proc(ignore := false, loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) { + if ignore { + return {}, loc + } else { + return temp_allocator_temp_begin(loc), loc + } +} + diff --git a/core/os/os2/env.odin b/core/os/os2/env.odin index bed4bebd9..c8d39b270 100644 --- a/core/os/os2/env.odin +++ b/core/os/os2/env.odin @@ -6,6 +6,7 @@ import "base:runtime" // It returns the value, which will be empty if the variable is not present // To distinguish between an empty value and an unset value, use lookup_env // NOTE: the value will be allocated with the supplied allocator +@(require_results) get_env :: proc(key: string, allocator: runtime.Allocator) -> string { value, _ := lookup_env(key, allocator) return value @@ -15,6 +16,7 @@ get_env :: proc(key: string, allocator: runtime.Allocator) -> string { // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true // Otherwise the returned value will be empty and the boolean will be false // NOTE: the value will be allocated with the supplied allocator +@(require_results) lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { return _lookup_env(key, allocator) } @@ -38,6 +40,7 @@ clear_env :: proc() { // environ returns a copy of strings representing the environment, in the form "key=value" // NOTE: the slice of strings and the strings with be allocated using the supplied allocator +@(require_results) environ :: proc(allocator: runtime.Allocator) -> []string { return _environ(allocator) } diff --git a/core/os/os2/env_linux.odin b/core/os/os2/env_linux.odin index eb463f22c..99dd00d90 100644 --- a/core/os/os2/env_linux.odin +++ b/core/os/os2/env_linux.odin @@ -2,29 +2,230 @@ package os2 import "base:runtime" +import "base:intrinsics" + +import "core:sync" +import "core:slice" +import "core:strings" + +// TODO: IF NO_CRT: +// Override the libc environment functions' weak linkage to +// allow us to interact with 3rd party code that DOES link +// to libc. Otherwise, our environment can be out of sync. +// ELSE: +// Just use the libc. + +NOT_FOUND :: -1 + +// the environment is a 0 delimited list of = strings +_env: [dynamic]string + +_env_mutex: sync.Mutex + +// We need to be able to figure out if the environment variable +// is contained in the original environment or not. This also +// serves as a flag to determine if we have built _env. +_org_env_begin: uintptr +_org_env_end: uintptr + +// Returns value + index location into _env +// or -1 if not found +_lookup :: proc(key: string) -> (value: string, idx: int) { + sync.mutex_lock(&_env_mutex) + defer sync.mutex_unlock(&_env_mutex) + + for entry, i in _env { + if k, v := _kv_from_entry(entry); k == key { + return v, i + } + } + return "", -1 +} _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { - //TODO + if _org_env_begin == 0 { + _build_env() + } + + if v, idx := _lookup(key); idx != -1 { + found = true + value, _ = clone_string(v, allocator) + } return } -_set_env :: proc(key, value: string) -> bool { - //TODO - return false +_set_env :: proc(key, v_new: string) -> bool { + if _org_env_begin == 0 { + _build_env() + } + + // all key values are stored as "key=value\x00" + kv_size := len(key) + len(v_new) + 2 + if v_curr, idx := _lookup(key); idx != NOT_FOUND { + if v_curr == v_new { + return true + } + sync.mutex_lock(&_env_mutex) + defer sync.mutex_unlock(&_env_mutex) + + unordered_remove(&_env, idx) + + if !_is_in_org_env(v_curr) { + // We allocated this key-value. Possibly resize and + // overwrite the value only. Otherwise, treat as if it + // wasn't in the environment in the first place. + k_addr, v_addr := _kv_addr_from_val(v_curr, key) + if len(v_new) > len(v_curr) { + k_addr = ([^]u8)(heap_resize(k_addr, kv_size)) + if k_addr == nil { + return false + } + v_addr = &k_addr[len(key) + 1] + } + intrinsics.mem_copy_non_overlapping(v_addr, raw_data(v_new), len(v_new)) + v_addr[len(v_new)] = 0 + + append(&_env, string(k_addr[:kv_size])) + return true + } + } + + k_addr := ([^]u8)(heap_alloc(kv_size)) + if k_addr == nil { + return false + } + intrinsics.mem_copy_non_overlapping(k_addr, raw_data(key), len(key)) + k_addr[len(key)] = '=' + + val_slice := k_addr[len(key) + 1:] + intrinsics.mem_copy_non_overlapping(&val_slice[0], raw_data(v_new), len(v_new)) + val_slice[len(v_new)] = 0 + + sync.mutex_lock(&_env_mutex) + append(&_env, string(k_addr[:kv_size - 1])) + sync.mutex_unlock(&_env_mutex) + return true } _unset_env :: proc(key: string) -> bool { - //TODO - return false + if _org_env_begin == 0 { + _build_env() + } + + v: string + i: int + if v, i = _lookup(key); i == -1 { + return false + } + + sync.mutex_lock(&_env_mutex) + unordered_remove(&_env, i) + sync.mutex_unlock(&_env_mutex) + + if _is_in_org_env(v) { + return true + } + + // if we got this far, the envrionment variable + // existed AND was allocated by us. + k_addr, _ := _kv_addr_from_val(v, key) + heap_free(k_addr) + return true } _clear_env :: proc() { - //TODO + sync.mutex_lock(&_env_mutex) + defer sync.mutex_unlock(&_env_mutex) + + for kv in _env { + if !_is_in_org_env(kv) { + heap_free(raw_data(kv)) + } + } + clear(&_env) + + // nothing resides in the original environment either + _org_env_begin = ~uintptr(0) + _org_env_end = ~uintptr(0) } _environ :: proc(allocator: runtime.Allocator) -> []string { - //TODO - return nil + if _org_env_begin == 0 { + _build_env() + } + env := make([]string, len(_env), allocator) + + sync.mutex_lock(&_env_mutex) + defer sync.mutex_unlock(&_env_mutex) + for entry, i in _env { + env[i], _ = clone_string(entry, allocator) + } + return env } +// The entire environment is stored as 0 terminated strings, +// so there is no need to clone/free individual variables +export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring { + if _org_env_begin == 0 { + // The environment has not been modified, so we can just + // send the original environment + org_env := _get_original_env() + n: int + for ; org_env[n] != nil; n += 1 {} + return slice.clone(org_env[:n + 1], allocator) + } + // NOTE: already terminated by nil pointer via + 1 + env := make([]cstring, len(_env) + 1, allocator) + + sync.mutex_lock(&_env_mutex) + defer sync.mutex_unlock(&_env_mutex) + for entry, i in _env { + env[i] = cstring(raw_data(entry)) + } + return env +} + +_build_env :: proc() { + sync.mutex_lock(&_env_mutex) + defer sync.mutex_unlock(&_env_mutex) + if _org_env_begin != 0 { + return + } + + _env = make(type_of(_env), heap_allocator()) + cstring_env := _get_original_env() + _org_env_begin = uintptr(rawptr(cstring_env[0])) + for i := 0; cstring_env[i] != nil; i += 1 { + bytes := ([^]u8)(cstring_env[i]) + n := len(cstring_env[i]) + _org_env_end = uintptr(&bytes[n]) + append(&_env, string(bytes[:n])) + } +} + +_get_original_env :: #force_inline proc() -> [^]cstring { + // essentially &argv[argc] which should be a nil pointer! + #no_bounds_check env: [^]cstring = &runtime.args__[len(runtime.args__)] + assert(env[0] == nil) + return &env[1] +} + +_kv_from_entry :: #force_inline proc(entry: string) -> (k, v: string) { + eq_idx := strings.index_byte(entry, '=') + if eq_idx == -1 { + return entry, "" + } + return entry[:eq_idx], entry[eq_idx + 1:] +} + +_kv_addr_from_val :: #force_inline proc(val: string, key: string) -> ([^]u8, [^]u8) { + v_addr := raw_data(val) + k_addr := ([^]u8)(&v_addr[-(len(key) + 1)]) + return k_addr, v_addr +} + +_is_in_org_env :: #force_inline proc(env_data: string) -> bool { + addr := uintptr(raw_data(env_data)) + return addr >= _org_env_begin && addr < _org_env_end +} diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 774af9e8f..39694b821 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -19,9 +19,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return "", true } - _TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD() - b := make([]u16, n+1, _temp_allocator()) + b := make([]u16, n+1, temp_allocator()) n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) if n == 0 { @@ -50,8 +50,8 @@ _unset_env :: proc(key: string) -> bool { } _clear_env :: proc() { - _TEMP_ALLOCATOR_GUARD() - envs := environ(_temp_allocator()) + TEMP_ALLOCATOR_GUARD() + envs := environ(temp_allocator()) for env in envs { for j in 1.. Error { + #partial switch errno { + case .NONE: + return nil + case .EPERM: + return .Permission_Denied + case .EEXIST: + return .Exist + case .ENOENT: + return .Not_Exist + } -_get_platform_error :: proc(res: int) -> Error { - errno := unix.get_errno(res) return Platform_Error(i32(errno)) } -_ok_or_error :: proc(res: int) -> Error { - return res >= 0 ? nil : _get_platform_error(res) -} - _error_string :: proc(errno: i32) -> string { - if errno == 0 { - return "" + if errno >= 0 && errno <= i32(max(linux.Errno)) { + return _errno_strings[linux.Errno(errno)] } - return "Error" + return "Unknown Error" } diff --git a/core/os/os2/errors_windows.odin b/core/os/os2/errors_windows.odin index 6500e7ccc..3572afb14 100644 --- a/core/os/os2/errors_windows.odin +++ b/core/os/os2/errors_windows.odin @@ -1,6 +1,8 @@ //+private package os2 +import "base:runtime" +import "core:slice" import win32 "core:sys/windows" _error_string :: proc(errno: i32) -> string { @@ -8,6 +10,14 @@ _error_string :: proc(errno: i32) -> string { if e == 0 { return "" } + + err := runtime.Type_Info_Enum_Value(e) + + ti := &runtime.type_info_base(type_info_of(win32.System_Error)).variant.(runtime.Type_Info_Enum) + if idx, ok := slice.binary_search(ti.values, err); ok { + return ti.names[idx] + } + // TODO(bill): _error_string for windows // FormatMessageW return "" diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 0efa53537..d4abf8f13 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -4,19 +4,57 @@ import "core:io" import "core:time" import "base:runtime" +/* + Type representing a file handle. + + This struct represents an OS-specific file-handle, which can be one of + the following: + - File + - Directory + - Pipe + - Named pipe + - Block Device + - Character device + - Symlink + - Socket + + See `File_Type` enum for more information on file types. +*/ File :: struct { - impl: _File, + impl: rawptr, stream: io.Stream, + fstat: Fstat_Callback, } -File_Mode :: distinct u32 -File_Mode_Dir :: File_Mode(1<<16) -File_Mode_Named_Pipe :: File_Mode(1<<17) -File_Mode_Device :: File_Mode(1<<18) -File_Mode_Char_Device :: File_Mode(1<<19) -File_Mode_Sym_Link :: File_Mode(1<<20) +/* + Type representing the type of a file handle. -File_Mode_Perm :: File_Mode(0o777) // Unix permision bits + **Note(windows)**: Socket handles can not be distinguished from + files, as they are just a normal file handle that is being treated by + a special driver. Windows also makes no distinction between block and + character devices. +*/ +File_Type :: enum { + // The type of a file could not be determined for the current platform. + Undetermined, + // Represents a regular file. + Regular, + // Represents a directory. + Directory, + // Represents a symbolic link. + Symlink, + // Represents a named pipe (FIFO). + Named_Pipe, + // Represents a socket. + // **Note(windows)**: Not returned on windows + Socket, + // Represents a block device. + // **Note(windows)**: On windows represents all devices. + Block_Device, + // Represents a character device. + // **Note(windows)**: Not returned on windows + Character_Device, +} File_Flags :: distinct bit_set[File_Flag; uint] File_Flag :: enum { @@ -28,7 +66,7 @@ File_Flag :: enum { Sync, Trunc, Sparse, - Close_On_Exec, + Inheritable, Unbuffered_IO, } @@ -42,31 +80,41 @@ O_EXCL :: File_Flags{.Excl} O_SYNC :: File_Flags{.Sync} O_TRUNC :: File_Flags{.Trunc} O_SPARSE :: File_Flags{.Sparse} -O_CLOEXEC :: File_Flags{.Close_On_Exec} +/* + If specified, the file handle is inherited upon the creation of a child + process. By default all handles are created non-inheritable. + **Note**: The standard file handles (stderr, stdout and stdin) are always + initialized as inheritable. +*/ +O_INHERITABLE :: File_Flags{.Inheritable} stdin: ^File = nil // OS-Specific stdout: ^File = nil // OS-Specific stderr: ^File = nil // OS-Specific - +@(require_results) create :: proc(name: string) -> (^File, Error) { - return open(name, {.Read, .Write, .Create}, File_Mode(0o777)) + return open(name, {.Read, .Write, .Create}, 0o777) } -open :: proc(name: string, flags := File_Flags{.Read}, perm := File_Mode(0o777)) -> (^File, Error) { +@(require_results) +open :: proc(name: string, flags := File_Flags{.Read}, perm := 0o777) -> (^File, Error) { return _open(name, flags, perm) } +@(require_results) new_file :: proc(handle: uintptr, name: string) -> ^File { return _new_file(handle, name) } +@(require_results) fd :: proc(f: ^File) -> uintptr { return _fd(f) } +@(require_results) name :: proc(f: ^File) -> string { return _name(f) } @@ -158,57 +206,73 @@ read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) chdir :: change_directory + change_directory :: proc(name: string) -> Error { return _chdir(name) } chmod :: change_mode -change_mode :: proc(name: string, mode: File_Mode) -> Error { + +change_mode :: proc(name: string, mode: int) -> Error { return _chmod(name, mode) } + chown :: change_owner + change_owner :: proc(name: string, uid, gid: int) -> Error { return _chown(name, uid, gid) } fchdir :: fchange_directory + fchange_directory :: proc(f: ^File) -> Error { return _fchdir(f) } + fchmod :: fchange_mode -fchange_mode :: proc(f: ^File, mode: File_Mode) -> Error { + +fchange_mode :: proc(f: ^File, mode: int) -> Error { return _fchmod(f, mode) } fchown :: fchange_owner + fchange_owner :: proc(f: ^File, uid, gid: int) -> Error { return _fchown(f, uid, gid) } lchown :: change_owner_do_not_follow_links + change_owner_do_not_follow_links :: proc(name: string, uid, gid: int) -> Error { return _lchown(name, uid, gid) } chtimes :: change_times + change_times :: proc(name: string, atime, mtime: time.Time) -> Error { return _chtimes(name, atime, mtime) } + fchtimes :: fchange_times + fchange_times :: proc(f: ^File, atime, mtime: time.Time) -> Error { return _fchtimes(f, atime, mtime) } +@(require_results) exists :: proc(path: string) -> bool { return _exists(path) } +@(require_results) is_file :: proc(path: string) -> bool { return _is_file(path) } is_dir :: is_directory + +@(require_results) is_directory :: proc(path: string) -> bool { return _is_dir(path) } @@ -218,15 +282,15 @@ copy_file :: proc(dst_path, src_path: string) -> Error { src := open(src_path) or_return defer close(src) - info := fstat(src, _file_allocator()) or_return - defer file_info_delete(info, _file_allocator()) - if info.is_directory { + info := fstat(src, file_allocator()) or_return + defer file_info_delete(info, file_allocator()) + if info.type == .Directory { return .Invalid_File } - dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & File_Mode_Perm) or_return + dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & 0o777) or_return defer close(dst) _, err := io.copy(to_writer(dst), to_reader(src)) return err -} \ No newline at end of file +} diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 61d320184..a8b20c6d9 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -3,370 +3,451 @@ package os2 import "core:io" import "core:time" -import "core:strings" import "base:runtime" -import "core:sys/unix" +import "core:sys/linux" -INVALID_HANDLE :: -1 - -_O_RDONLY :: 0o00000000 -_O_WRONLY :: 0o00000001 -_O_RDWR :: 0o00000002 -_O_CREAT :: 0o00000100 -_O_EXCL :: 0o00000200 -_O_NOCTTY :: 0o00000400 -_O_TRUNC :: 0o00001000 -_O_APPEND :: 0o00002000 -_O_NONBLOCK :: 0o00004000 -_O_LARGEFILE :: 0o00100000 -_O_DIRECTORY :: 0o00200000 -_O_NOFOLLOW :: 0o00400000 -_O_SYNC :: 0o04010000 -_O_CLOEXEC :: 0o02000000 -_O_PATH :: 0o10000000 - -_AT_FDCWD :: -100 - -_CSTRING_NAME_HEAP_THRESHOLD :: 512 - -_File :: struct { +File_Impl :: struct { + file: File, name: string, - fd: int, + fd: linux.Fd, allocator: runtime.Allocator, } +_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, + }, + fstat = _fstat, +} + +@init +_standard_stream_init :: proc() { + // cannot define these manually because cyclic reference + _stdin.stream.data = &_stdin + _stdout.stream.data = &_stdout + _stderr.stream.data = &_stderr + + stdin = &_stdin + stdout = &_stdout + stderr = &_stderr +} + _file_allocator :: proc() -> runtime.Allocator { return heap_allocator() } -_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (^File, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) +_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { + TEMP_ALLOCATOR_GUARD() + name_cstr := temp_cstring(name) or_return // Just default to using O_NOCTTY because needing to open a controlling // terminal would be incredibly rare. This has no effect on files while // allowing us to open serial devices. - flags_i: int = _O_NOCTTY + sys_flags: linux.Open_Flags = {.NOCTTY, .CLOEXEC} switch flags & O_RDONLY|O_WRONLY|O_RDWR { - case O_RDONLY: flags_i = _O_RDONLY - case O_WRONLY: flags_i = _O_WRONLY - case O_RDWR: flags_i = _O_RDWR + case O_RDONLY: + case O_WRONLY: sys_flags += {.WRONLY} + case O_RDWR: sys_flags += {.RDWR} } + if .Append in flags { sys_flags += {.APPEND} } + if .Create in flags { sys_flags += {.CREAT} } + if .Excl in flags { sys_flags += {.EXCL} } + if .Sync in flags { sys_flags += {.DSYNC} } + if .Trunc in flags { sys_flags += {.TRUNC} } + if .Inheritable in flags { sys_flags -= {.CLOEXEC} } - flags_i |= (_O_APPEND * int(.Append in flags)) - flags_i |= (_O_CREAT * int(.Create in flags)) - flags_i |= (_O_EXCL * int(.Excl in flags)) - flags_i |= (_O_SYNC * int(.Sync in flags)) - flags_i |= (_O_TRUNC * int(.Trunc in flags)) - flags_i |= (_O_CLOEXEC * int(.Close_On_Exec in flags)) - - fd := unix.sys_open(name_cstr, flags_i, uint(perm)) - if fd < 0 { - return nil, _get_platform_error(fd) + fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)u32(perm)) + if errno != .NONE { + return nil, _get_platform_error(errno) } return _new_file(uintptr(fd), name), nil } -_new_file :: proc(fd: uintptr, _: string) -> ^File { - file := new(File, _file_allocator()) - file.impl.fd = int(fd) - file.impl.allocator = _file_allocator() - file.impl.name = _get_full_path(file.impl.fd, file.impl.allocator) - file.stream = { - data = file, +_new_file :: proc(fd: uintptr, _: string = "") -> ^File { + impl := new(File_Impl, file_allocator()) + impl.file.impl = impl + impl.fd = linux.Fd(fd) + impl.allocator = file_allocator() + impl.name = _get_full_path(impl.fd, impl.allocator) + impl.file.stream = { + data = impl, procedure = _file_stream_proc, } - return file + impl.file.fstat = _fstat + return &impl.file } -_destroy :: proc(f: ^File) -> Error { +_destroy :: proc(f: ^File_Impl) -> Error { if f == nil { return nil } - delete(f.impl.name, f.impl.allocator) - free(f, f.impl.allocator) + a := f.allocator + delete(f.name, a) + free(f, a) return nil } -_close :: proc(f: ^File) -> Error { - res := unix.sys_close(f.impl.fd) - return _ok_or_error(res) +_close :: proc(f: ^File_Impl) -> Error { + if f == nil{ + return nil + } + errno := linux.close(f.fd) + if errno == .EBADF { // avoid possible double free + return _get_platform_error(errno) + } + _destroy(f) + return _get_platform_error(errno) } _fd :: proc(f: ^File) -> uintptr { - if f == nil { + if f == nil || f.impl == nil { return ~uintptr(0) } - return uintptr(f.impl.fd) + impl := (^File_Impl)(f.impl) + return uintptr(impl.fd) } _name :: proc(f: ^File) -> string { - return f.impl.name if f != nil else "" + return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else "" } -_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { - res := unix.sys_lseek(f.impl.fd, offset, int(whence)) - if res < 0 { - return -1, _get_platform_error(int(res)) +_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { + n, errno := linux.lseek(f.fd, offset, linux.Seek_Whence(whence)) + if errno != .NONE { + return -1, _get_platform_error(errno) } - return res, nil + return n, nil } -_read :: proc(f: ^File, p: []byte) -> (i64, Error) { +_read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { if len(p) == 0 { return 0, nil } - n := unix.sys_read(f.impl.fd, &p[0], len(p)) - if n < 0 { - return -1, _get_platform_error(n) + n, errno := linux.read(f.fd, p[:]) + if errno != .NONE { + return -1, _get_platform_error(errno) + } + return i64(n), n == 0 ? io.Error.EOF : nil +} + +_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { + if offset < 0 { + return 0, .Invalid_Offset + } + n, errno := linux.pread(f.fd, p[:], offset) + if errno != .NONE { + return -1, _get_platform_error(errno) + } + if n == 0 { + return 0, .EOF } return i64(n), nil } -_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { - if offset < 0 { - return 0, .Invalid_Offset - } - - b, offset := p, offset - for len(b) > 0 { - m := unix.sys_pread(f.impl.fd, &b[0], len(b), offset) - if m < 0 { - return -1, _get_platform_error(m) - } - n += i64(m) - b = b[m:] - offset += i64(m) - } - return -} - -_write :: proc(f: ^File, p: []byte) -> (i64, Error) { +_write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { if len(p) == 0 { return 0, nil } - n := unix.sys_write(f.impl.fd, &p[0], uint(len(p))) - if n < 0 { - return -1, _get_platform_error(n) + n, errno := linux.write(f.fd, p[:]) + if errno != .NONE { + return -1, _get_platform_error(errno) } return i64(n), nil } -_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { +_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { if offset < 0 { return 0, .Invalid_Offset } - - b, offset := p, offset - for len(b) > 0 { - m := unix.sys_pwrite(f.impl.fd, &b[0], len(b), offset) - if m < 0 { - return -1, _get_platform_error(m) - } - n += i64(m) - b = b[m:] - offset += i64(m) + n, errno := linux.pwrite(f.fd, p[:], offset) + if errno != .NONE { + return -1, _get_platform_error(errno) } - return + return i64(n), nil } -_file_size :: proc(f: ^File) -> (n: i64, err: Error) { - s: _Stat = --- - res := unix.sys_fstat(f.impl.fd, &s) - if res < 0 { - return -1, _get_platform_error(res) +_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { + s: linux.Stat = --- + errno := linux.fstat(f.fd, &s) + if errno != .NONE { + return -1, _get_platform_error(errno) } - return s.size, nil + return i64(s.size), nil } _sync :: proc(f: ^File) -> Error { - return _ok_or_error(unix.sys_fsync(f.impl.fd)) + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.fsync(impl.fd)) } -_flush :: proc(f: ^File) -> Error { - return _ok_or_error(unix.sys_fsync(f.impl.fd)) +_flush :: proc(f: ^File_Impl) -> Error { + return _get_platform_error(linux.fsync(f.fd)) } _truncate :: proc(f: ^File, size: i64) -> Error { - return _ok_or_error(unix.sys_ftruncate(f.impl.fd, size)) + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.ftruncate(impl.fd, size)) } _remove :: proc(name: string) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + TEMP_ALLOCATOR_GUARD() + name_cstr := temp_cstring(name) or_return - fd := unix.sys_open(name_cstr, int(File_Flags.Read)) - if fd < 0 { - return _get_platform_error(fd) + fd, errno := linux.open(name_cstr, {.NOFOLLOW}) + #partial switch (errno) { + case .ELOOP: /* symlink */ + case .NONE: + defer linux.close(fd) + if _is_dir_fd(fd) { + return _get_platform_error(linux.rmdir(name_cstr)) + } + case: + return _get_platform_error(errno) } - defer unix.sys_close(fd) - if _is_dir_fd(fd) { - return _ok_or_error(unix.sys_rmdir(name_cstr)) - } - return _ok_or_error(unix.sys_unlink(name_cstr)) + return _get_platform_error(linux.unlink(name_cstr)) } _rename :: proc(old_name, new_name: string) -> Error { - old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) - new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) + TEMP_ALLOCATOR_GUARD() + old_name_cstr := temp_cstring(old_name) or_return + new_name_cstr := temp_cstring(new_name) or_return - return _ok_or_error(unix.sys_rename(old_name_cstr, new_name_cstr)) + return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr)) } _link :: proc(old_name, new_name: string) -> Error { - old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) - new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) + TEMP_ALLOCATOR_GUARD() + old_name_cstr := temp_cstring(old_name) or_return + new_name_cstr := temp_cstring(new_name) or_return - return _ok_or_error(unix.sys_link(old_name_cstr, new_name_cstr)) + return _get_platform_error(linux.link(old_name_cstr, new_name_cstr)) } _symlink :: proc(old_name, new_name: string) -> Error { - old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) - new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) - - return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr)) + TEMP_ALLOCATOR_GUARD() + old_name_cstr := temp_cstring(old_name) or_return + new_name_cstr := temp_cstring(new_name) or_return + return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr)) } _read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (string, Error) { bufsz : uint = 256 buf := make([]byte, bufsz, allocator) for { - rc := unix.sys_readlink(name_cstr, &(buf[0]), bufsz) - if rc < 0 { - delete(buf) - return "", _get_platform_error(rc) - } else if rc == int(bufsz) { + sz, errno := linux.readlink(name_cstr, buf[:]) + if errno != .NONE { + delete(buf, allocator) + return "", _get_platform_error(errno) + } else if sz == int(bufsz) { bufsz *= 2 - delete(buf) + delete(buf, allocator) buf = make([]byte, bufsz, allocator) } else { - return strings.string_from_ptr(&buf[0], rc), nil + return string(buf[:sz]), nil } } } -_read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) { + TEMP_ALLOCATOR_GUARD() + name_cstr := temp_cstring(name) or_return return _read_link_cstr(name_cstr, allocator) } -_unlink :: proc(name: string) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) - return _ok_or_error(unix.sys_unlink(name_cstr)) -} - _chdir :: proc(name: string) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) - return _ok_or_error(unix.sys_chdir(name_cstr)) + TEMP_ALLOCATOR_GUARD() + name_cstr := temp_cstring(name) or_return + return _get_platform_error(linux.chdir(name_cstr)) } _fchdir :: proc(f: ^File) -> Error { - return _ok_or_error(unix.sys_fchdir(f.impl.fd)) + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.fchdir(impl.fd)) } -_chmod :: proc(name: string, mode: File_Mode) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) - return _ok_or_error(unix.sys_chmod(name_cstr, uint(mode))) +_chmod :: proc(name: string, mode: int) -> Error { + TEMP_ALLOCATOR_GUARD() + name_cstr := temp_cstring(name) or_return + return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode)))) } -_fchmod :: proc(f: ^File, mode: File_Mode) -> Error { - return _ok_or_error(unix.sys_fchmod(f.impl.fd, uint(mode))) +_fchmod :: proc(f: ^File, mode: int) -> Error { + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.fchmod(impl.fd, transmute(linux.Mode)(u32(mode)))) } // NOTE: will throw error without super user priviledges _chown :: proc(name: string, uid, gid: int) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) - return _ok_or_error(unix.sys_chown(name_cstr, uid, gid)) + TEMP_ALLOCATOR_GUARD() + name_cstr := temp_cstring(name) or_return + return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid))) } // NOTE: will throw error without super user priviledges _lchown :: proc(name: string, uid, gid: int) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) - return _ok_or_error(unix.sys_lchown(name_cstr, uid, gid)) + TEMP_ALLOCATOR_GUARD() + name_cstr := temp_cstring(name) or_return + return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid))) } // NOTE: will throw error without super user priviledges _fchown :: proc(f: ^File, uid, gid: int) -> Error { - return _ok_or_error(unix.sys_fchown(f.impl.fd, uid, gid)) + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.fchown(impl.fd, linux.Uid(uid), linux.Gid(gid))) } _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) - times := [2]Unix_File_Time { - { atime._nsec, 0 }, - { mtime._nsec, 0 }, + TEMP_ALLOCATOR_GUARD() + name_cstr := temp_cstring(name) or_return + times := [2]linux.Time_Spec { + { + uint(atime._nsec) / uint(time.Second), + uint(atime._nsec) % uint(time.Second), + }, + { + uint(mtime._nsec) / uint(time.Second), + uint(mtime._nsec) % uint(time.Second), + }, } - return _ok_or_error(unix.sys_utimensat(_AT_FDCWD, name_cstr, ×, 0)) + return _get_platform_error(linux.utimensat(linux.AT_FDCWD, name_cstr, ×[0], nil)) } _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { - times := [2]Unix_File_Time { - { atime._nsec, 0 }, - { mtime._nsec, 0 }, + times := [2]linux.Time_Spec { + { + uint(atime._nsec) / uint(time.Second), + uint(atime._nsec) % uint(time.Second), + }, + { + uint(mtime._nsec) / uint(time.Second), + uint(mtime._nsec) % uint(time.Second), + }, } - return _ok_or_error(unix.sys_utimensat(f.impl.fd, nil, ×, 0)) + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.utimensat(impl.fd, nil, ×[0], nil)) } _exists :: proc(name: string) -> bool { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) - return unix.sys_access(name_cstr, F_OK) == 0 + TEMP_ALLOCATOR_GUARD() + name_cstr, _ := temp_cstring(name) + res, errno := linux.access(name_cstr, linux.F_OK) + return !res && errno == .NONE } _is_file :: proc(name: string) -> bool { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) - s: _Stat - res := unix.sys_stat(name_cstr, &s) - if res < 0 { + TEMP_ALLOCATOR_GUARD() + name_cstr, _ := temp_cstring(name) + s: linux.Stat + if linux.stat(name_cstr, &s) != .NONE { return false } - return S_ISREG(s.mode) + return linux.S_ISREG(s.mode) } -_is_file_fd :: proc(fd: int) -> bool { - s: _Stat - res := unix.sys_fstat(fd, &s) - if res < 0 { // error +_is_file_fd :: proc(fd: linux.Fd) -> bool { + s: linux.Stat + if linux.fstat(fd, &s) != .NONE { return false } - return S_ISREG(s.mode) + return linux.S_ISREG(s.mode) } _is_dir :: proc(name: string) -> bool { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) - s: _Stat - res := unix.sys_stat(name_cstr, &s) - if res < 0 { + TEMP_ALLOCATOR_GUARD() + name_cstr, _ := temp_cstring(name) + s: linux.Stat + if linux.stat(name_cstr, &s) != .NONE { return false } - return S_ISDIR(s.mode) + return linux.S_ISDIR(s.mode) } -_is_dir_fd :: proc(fd: int) -> bool { - s: _Stat - res := unix.sys_fstat(fd, &s) - if res < 0 { // error +_is_dir_fd :: proc(fd: linux.Fd) -> bool { + s: linux.Stat + if linux.fstat(fd, &s) != .NONE { return false } - return S_ISDIR(s.mode) + return linux.S_ISDIR(s.mode) } -// Ideally we want to use the temp_allocator. PATH_MAX on Linux is commonly -// defined as 512, however, it is well known that paths can exceed that limit. -// So, in theory you could have a path larger than the entire temp_allocator's -// buffer. Therefor, any large paths will use context.allocator. -@(private="file") -_temp_name_to_cstring :: proc(name: string) -> (cname: cstring) { - return strings.clone_to_cstring(name, context.temp_allocator) +/* 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. + */ +_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) + return _read_entire_pseudo_file_cstring(name_cstr, allocator) } +_read_entire_pseudo_file_cstring :: proc(name: cstring, allocator: runtime.Allocator) -> ([]u8, Error) { + fd, errno := linux.open(name, {}) + if errno != .NONE { + return nil, _get_platform_error(errno) + } + defer linux.close(fd) + + BUF_SIZE_STEP :: 128 + contents := make([dynamic]u8, 0, BUF_SIZE_STEP, allocator) + + n: int + i: int + for { + resize(&contents, i + BUF_SIZE_STEP) + n, errno = linux.read(fd, contents[i:i+BUF_SIZE_STEP]) + if errno != .NONE { + delete(contents) + return nil, _get_platform_error(errno) + } + if n < BUF_SIZE_STEP { + break + } + i += BUF_SIZE_STEP + } + + resize(&contents, i + n) + + return contents[:], nil +} @(private="package") _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { - f := (^File)(stream_data) + f := (^File_Impl)(stream_data) ferr: Error - i: int switch mode { case .Read: n, ferr = _read(f, p) diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index 0708f708e..ca0e8c940 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -76,6 +76,7 @@ read_entire_file :: proc{ read_entire_file_from_file, } +@(require_results) read_entire_file_from_path :: proc(name: string, allocator: runtime.Allocator) -> (data: []byte, err: Error) { f, ferr := open(name) if ferr != nil { @@ -85,14 +86,15 @@ read_entire_file_from_path :: proc(name: string, allocator: runtime.Allocator) - return read_entire_file_from_file(f, allocator) } +@(require_results) read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (data: []byte, err: Error) { size: int has_size := true - if size64, err := file_size(f); err == nil { + if size64, serr := file_size(f); serr == nil { if i64(int(size64)) != size64 { size = int(size64) } - } else if err == .No_Size { + } else if serr == .No_Size { has_size = false } else { return @@ -135,7 +137,8 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d } } -write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate := true) -> Error { +@(require_results) +write_entire_file :: proc(name: string, data: []byte, perm: int, truncate := true) -> Error { flags := O_WRONLY|O_CREATE if truncate { flags |= O_TRUNC diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index fc3cebaea..97061f281 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -6,7 +6,6 @@ import "base:runtime" import "core:io" import "core:mem" import "core:sync" -import "core:strings" import "core:time" import "core:unicode/utf16" import win32 "core:sys/windows" @@ -17,61 +16,20 @@ S_IWRITE :: 0o200 _ERROR_BAD_NETPATH :: 53 MAX_RW :: 1<<30 -_file_allocator :: proc() -> runtime.Allocator { - return heap_allocator() -} -_temp_allocator_proc :: runtime.arena_allocator_proc - -@(private="file", thread_local) -_global_default_temp_allocator_arena: runtime.Arena - -_temp_allocator :: proc() -> runtime.Allocator { - return runtime.Allocator{ - procedure = _temp_allocator_proc, - data = &_global_default_temp_allocator_arena, - } -} - -@(require_results) -_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) { - temp = runtime.arena_temp_begin(&_global_default_temp_allocator_arena, loc) - return -} - -_temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_location) { - runtime.arena_temp_end(temp, loc) -} - -@(fini, private) -_destroy_temp_allocator_fini :: proc() { - runtime.arena_destroy(&_global_default_temp_allocator_arena) - _global_default_temp_allocator_arena = {} -} - -@(deferred_out=_temp_allocator_temp_end) -_TEMP_ALLOCATOR_GUARD :: #force_inline proc(ignore := false, loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) { - if ignore { - return {}, loc - } else { - return _temp_allocator_temp_begin(loc), loc - } -} - - - - -_File_Kind :: enum u8 { +File_Impl_Kind :: enum u8 { File, Console, Pipe, } -_File :: struct { +File_Impl :: struct { + file: File, + fd: rawptr, name: string, wname: win32.wstring, - kind: _File_Kind, + kind: File_Impl_Kind, allocator: runtime.Allocator, @@ -79,11 +37,25 @@ _File :: struct { p_mutex: sync.Mutex, // pread pwrite calls } +@(init) +init_std_files :: proc() { + stdin = new_file(uintptr(win32.GetStdHandle(win32.STD_INPUT_HANDLE)), "") + stdout = new_file(uintptr(win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)), "") + stderr = new_file(uintptr(win32.GetStdHandle(win32.STD_ERROR_HANDLE)), "") +} +@(fini) +fini_std_files :: proc() { + close(stdin) + close(stdout) + close(stderr) +} + + _handle :: proc(f: ^File) -> win32.HANDLE { return win32.HANDLE(_fd(f)) } -_open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (handle: uintptr, err: Error) { +_open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: uintptr, err: Error) { if len(name) == 0 { err = .Not_Exist return @@ -105,11 +77,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han access |= win32.FILE_APPEND_DATA } share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE) - sa: ^win32.SECURITY_ATTRIBUTES - if .Close_On_Exec not_in flags { - sa = &win32.SECURITY_ATTRIBUTES{} - sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) - sa.bInheritHandle = true + sa := win32.SECURITY_ATTRIBUTES { + nLength = size_of(win32.SECURITY_ATTRIBUTES), + bInheritHandle = .Inheritable in flags, } create_mode: u32 = win32.OPEN_EXISTING @@ -131,7 +101,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han // NOTE(bill): Open has just asked to create a file in read-only mode. // If the file already exists, to make it akin to a *nix open call, // the call preserves the existing permissions. - h := win32.CreateFileW(path, access, share_mode, sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil) + h := win32.CreateFileW(path, access, share_mode, &sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil) if h == win32.INVALID_HANDLE { switch e := win32.GetLastError(); e { case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND: @@ -144,7 +114,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han } } } - h := win32.CreateFileW(path, access, share_mode, sa, create_mode, attrs, nil) + h := win32.CreateFileW(path, access, share_mode, &sa, create_mode, attrs, nil) if h == win32.INVALID_HANDLE { return 0, _get_platform_error() } @@ -152,9 +122,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han } -_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) { +_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { flags := flags if flags != nil else {.Read} - handle := _open_internal(name, flags + {.Close_On_Exec}, perm) or_return + handle := _open_internal(name, flags, perm) or_return return _new_file(handle, name), nil } @@ -162,75 +132,81 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File { if handle == INVALID_HANDLE { return nil } - f := new(File, _file_allocator()) + impl := new(File_Impl, file_allocator()) + impl.file.impl = impl - f.impl.allocator = _file_allocator() - f.impl.fd = rawptr(handle) - f.impl.name = strings.clone(name, f.impl.allocator) - f.impl.wname = win32.utf8_to_wstring(name, f.impl.allocator) + impl.allocator = file_allocator() + impl.fd = rawptr(handle) + impl.name, _ = clone_string(name, impl.allocator) + impl.wname = win32.utf8_to_wstring(name, impl.allocator) - handle := _handle(f) - kind := _File_Kind.File + handle := _handle(&impl.file) + kind := File_Impl_Kind.File if m: u32; win32.GetConsoleMode(handle, &m) { kind = .Console } if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE { kind = .Pipe } - f.impl.kind = kind + impl.kind = kind - f.stream = { - data = f, + impl.file.stream = { + data = impl, procedure = _file_stream_proc, } + impl.file.fstat = _fstat - return f + return &impl.file } _fd :: proc(f: ^File) -> uintptr { - if f == nil { + if f == nil || f.impl == nil { return INVALID_HANDLE } - return uintptr(f.impl.fd) + return uintptr((^File_Impl)(f.impl).fd) } -_destroy :: proc(f: ^File) -> Error { +_destroy :: proc(f: ^File_Impl) -> Error { if f == nil { return nil } - a := f.impl.allocator - free(f.impl.wname, a) - delete(f.impl.name, a) - free(f, a) + a := f.allocator + err0 := free(f.wname, a) + err1 := delete(f.name, a) + err2 := free(f, a) + err0 or_return + err1 or_return + err2 or_return return nil } -_close :: proc(f: ^File) -> Error { - if f == nil { +_close :: proc(f: ^File_Impl) -> Error { + if f == nil { return nil } - if !win32.CloseHandle(win32.HANDLE(f.impl.fd)) { + if !win32.CloseHandle(win32.HANDLE(f.fd)) { return .Closed } return _destroy(f) } _name :: proc(f: ^File) -> string { - return f.impl.name if f != nil else "" + return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else "" } -_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { - handle := _handle(f) +_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { + handle := _handle(&f.file) if handle == win32.INVALID_HANDLE { return 0, .Invalid_File } - if f.impl.kind == .Pipe { + + if f.kind == .Pipe { return 0, .Invalid_File } - sync.guard(&f.impl.rw_mutex) + sync.guard(&f.rw_mutex) w: u32 switch whence { @@ -248,7 +224,7 @@ _seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Er return i64(hi)<<32 + i64(dw_ptr), nil } -_read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { +_read :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { if len(b) == 0 { return 0, nil @@ -299,22 +275,22 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { return } - handle := _handle(f) + handle := _handle(&f.file) single_read_length: win32.DWORD total_read: int length := len(p) - sync.shared_guard(&f.impl.rw_mutex) // multiple readers + sync.shared_guard(&f.rw_mutex) // multiple readers - if sync.guard(&f.impl.p_mutex) { + if sync.guard(&f.p_mutex) { to_read := min(win32.DWORD(length), MAX_RW) ok: win32.BOOL - if f.impl.kind == .Console { - n, err := read_console(handle, p[total_read:][:to_read]) + if f.kind == .Console { + n, cerr := read_console(handle, p[total_read:][:to_read]) total_read += n - if err != nil { - return i64(total_read), err + if cerr != nil { + return i64(total_read), cerr } } else { ok = win32.ReadFile(handle, &p[total_read], to_read, &single_read_length, nil) @@ -330,15 +306,15 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { return i64(total_read), err } -_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { - pread :: proc(f: ^File, data: []byte, offset: i64) -> (n: i64, err: Error) { +_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) { + pread :: proc(f: ^File_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) { buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] } - curr_offset := seek(f, offset, .Current) or_return - defer seek(f, curr_offset, .Start) + curr_offset := _seek(f, offset, .Current) or_return + defer _seek(f, curr_offset, .Start) o := win32.OVERLAPPED{ OffsetHigh = u32(offset>>32), @@ -347,7 +323,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { // TODO(bill): Determine the correct behaviour for consoles - h := _handle(f) + h := _handle(&f.file) done: win32.DWORD if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { err = _get_platform_error() @@ -357,7 +333,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } - sync.guard(&f.impl.p_mutex) + sync.guard(&f.p_mutex) p, offset := p, offset for len(p) > 0 { @@ -369,7 +345,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } -_write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { +_write :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { if len(p) == 0 { return } @@ -378,9 +354,9 @@ _write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { total_write: i64 length := i64(len(p)) - handle := _handle(f) + handle := _handle(&f.file) - sync.guard(&f.impl.rw_mutex) + sync.guard(&f.rw_mutex) for total_write < length { remaining := length - total_write to_write := win32.DWORD(min(i32(remaining), MAX_RW)) @@ -396,22 +372,22 @@ _write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { return i64(total_write), nil } -_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { - pwrite :: proc(f: ^File, data: []byte, offset: i64) -> (n: i64, err: Error) { +_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) { + pwrite :: proc(f: ^File_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) { buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] } - curr_offset := seek(f, offset, .Current) or_return - defer seek(f, curr_offset, .Start) + curr_offset := _seek(f, offset, .Current) or_return + defer _seek(f, curr_offset, .Start) o := win32.OVERLAPPED{ OffsetHigh = u32(offset>>32), Offset = u32(offset), } - h := _handle(f) + h := _handle(&f.file) done: win32.DWORD if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { err = _get_platform_error() @@ -421,7 +397,7 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } - sync.guard(&f.impl.p_mutex) + sync.guard(&f.p_mutex) p, offset := p, offset for len(p) > 0 { m := pwrite(f, p, offset) or_return @@ -432,12 +408,12 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } -_file_size :: proc(f: ^File) -> (n: i64, err: Error) { +_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { length: win32.LARGE_INTEGER - if f.impl.kind == .Pipe { + if f.kind == .Pipe { return 0, .No_Size } - handle := _handle(f) + handle := _handle(&f.file) if !win32.GetFileSizeEx(handle, &length) { err = _get_platform_error() } @@ -447,11 +423,14 @@ _file_size :: proc(f: ^File) -> (n: i64, err: Error) { _sync :: proc(f: ^File) -> Error { - return _flush(f) + if f != nil && f.impl != nil { + return _flush((^File_Impl)(f.impl)) + } + return nil } -_flush :: proc(f: ^File) -> Error { - handle := _handle(f) +_flush :: proc(f: ^File_Impl) -> Error { + handle := _handle(&f.file) if !win32.FlushFileBuffers(handle) { return _get_platform_error() } @@ -459,7 +438,7 @@ _flush :: proc(f: ^File) -> Error { } _truncate :: proc(f: ^File, size: i64) -> Error { - if f == nil { + if f == nil || f.impl == nil { return nil } curr_off := seek(f, 0, .Current) or_return @@ -519,7 +498,6 @@ _rename :: proc(old_path, new_path: string) -> Error { } - _link :: proc(old_name, new_name: string) -> Error { o := _fix_long_path(old_name) n := _fix_long_path(new_name) @@ -583,9 +561,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st return "", _get_platform_error() } - _TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD() - buf := make([]u16, n+1, _temp_allocator()) + buf := make([]u16, n+1, temp_allocator()) n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS) if n == 0 { return "", _get_platform_error() @@ -646,17 +624,18 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er _fchdir :: proc(f: ^File) -> Error { - if f == nil { + if f == nil || f.impl == nil { return nil } - if !win32.SetCurrentDirectoryW(f.impl.wname) { + impl := (^File_Impl)(f.impl) + if !win32.SetCurrentDirectoryW(impl.wname) { return _get_platform_error() } return nil } -_fchmod :: proc(f: ^File, mode: File_Mode) -> Error { - if f == nil { +_fchmod :: proc(f: ^File, mode: int) -> Error { + if f == nil || f.impl == nil { return nil } d: win32.BY_HANDLE_FILE_INFORMATION @@ -690,7 +669,7 @@ _chdir :: proc(name: string) -> Error { return nil } -_chmod :: proc(name: string, mode: File_Mode) -> Error { +_chmod :: proc(name: string, mode: int) -> Error { f := open(name, {.Write}) or_return defer close(f) return _fchmod(f, mode) @@ -711,7 +690,7 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { return _fchtimes(f, atime, mtime) } _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { - if f == nil { + if f == nil || f.impl == nil { return nil } d: win32.BY_HANDLE_FILE_INFORMATION @@ -738,8 +717,6 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { return nil } - - _exists :: proc(path: string) -> bool { wpath := _fix_long_path(path) attribs := win32.GetFileAttributesW(wpath) @@ -767,7 +744,7 @@ _is_dir :: proc(path: string) -> bool { @(private="package") _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { - f := (^File)(stream_data) + f := (^File_Impl)(stream_data) ferr: Error switch mode { case .Read: @@ -798,14 +775,12 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, ferr = _flush(f) err = error_to_io_error(ferr) return - case .Close: + 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, .Query}) - case .Destroy: - return 0, .Empty } return 0, .Empty } diff --git a/core/os/os2/heap.odin b/core/os/os2/heap.odin index a07a0d618..e0cffaf0d 100644 --- a/core/os/os2/heap.odin +++ b/core/os/os2/heap.odin @@ -2,6 +2,7 @@ package os2 import "base:runtime" +@(require_results) heap_allocator :: proc() -> runtime.Allocator { return runtime.Allocator{ procedure = heap_allocator_proc, @@ -10,6 +11,7 @@ heap_allocator :: proc() -> runtime.Allocator { } +@(require_results) heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, runtime.Allocator_Error) { diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index bb4acba13..11cf5ab41 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -1,7 +1,7 @@ //+private package os2 -import "core:sys/unix" +import "core:sys/linux" import "core:sync" import "core:mem" @@ -97,9 +97,8 @@ CURRENTLY_ACTIVE :: (^^Region)(~uintptr(0)) FREE_LIST_ENTRIES_PER_BLOCK :: BLOCK_SIZE / size_of(u16) -MMAP_FLAGS :: unix.MAP_ANONYMOUS | unix.MAP_PRIVATE -MMAP_PROT :: unix.PROT_READ | unix.PROT_WRITE - +MMAP_FLAGS : linux.Map_Flags : {.ANONYMOUS, .PRIVATE} +MMAP_PROT : linux.Mem_Protection : {.READ, .WRITE} @thread_local _local_region: ^Region global_regions: ^Region @@ -324,11 +323,11 @@ heap_free :: proc(memory: rawptr) { // Regions // _new_region :: proc() -> ^Region #no_bounds_check { - res := unix.sys_mmap(nil, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0) - if res < 0 { + ptr, errno := linux.mmap(0, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0) + if errno != .NONE { return nil } - new_region := (^Region)(uintptr(res)) + new_region := (^Region)(ptr) new_region.hdr.local_addr = CURRENTLY_ACTIVE new_region.hdr.reset_addr = &_local_region @@ -634,8 +633,8 @@ _region_free_list_remove :: proc(region: ^Region, free_idx: u16) #no_bounds_chec // _direct_mmap_alloc :: proc(size: int) -> rawptr { mmap_size := _round_up_to_nearest(size + BLOCK_SIZE, PAGE_SIZE) - new_allocation := unix.sys_mmap(nil, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0) - if new_allocation < 0 && new_allocation > -4096 { + new_allocation, errno := linux.mmap(0, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0) + if errno != .NONE { return nil } @@ -655,13 +654,8 @@ _direct_mmap_resize :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr return mem.ptr_offset(alloc, 1) } - new_allocation := unix.sys_mremap( - alloc, - uint(old_mmap_size), - uint(new_mmap_size), - unix.MREMAP_MAYMOVE, - ) - if new_allocation < 0 && new_allocation > -4096 { + new_allocation, errno := linux.mremap(alloc, uint(old_mmap_size), uint(new_mmap_size), {.MAYMOVE}) + if errno != .NONE { return nil } @@ -702,7 +696,7 @@ _direct_mmap_to_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawp _direct_mmap_free :: proc(alloc: ^Allocation_Header) { requested := int(alloc.requested & REQUESTED_MASK) mmap_size := _round_up_to_nearest(requested + BLOCK_SIZE, PAGE_SIZE) - unix.sys_munmap(alloc, uint(mmap_size)) + linux.munmap(alloc, uint(mmap_size)) } // diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin new file mode 100644 index 000000000..e26cf7439 --- /dev/null +++ b/core/os/os2/internal_util.odin @@ -0,0 +1,128 @@ +//+private +package os2 + +import "base:intrinsics" +import "base:runtime" + + +// Splits pattern by the last wildcard "*", if it exists, and returns the prefix and suffix +// parts which are split by the last "*" +@(require_results) +_prefix_and_suffix :: proc(pattern: string) -> (prefix, suffix: string, err: Error) { + for i in 0..= 0; i -= 1 { + if pattern[i] == '*' { + prefix, suffix = pattern[:i], pattern[i+1:] + break + } + } + return +} + +@(require_results) +clone_string :: proc(s: string, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + buf := make([]byte, len(s), allocator) or_return + copy(buf, s) + return string(buf), nil +} + + +@(require_results) +clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstring, err: runtime.Allocator_Error) { + res = "" // do not use a `nil` cstring + buf := make([]byte, len(s)+1, allocator) or_return + copy(buf, s) + buf[len(s)] = 0 + return cstring(&buf[0]), nil +} + +@(require_results) +temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) { + return clone_to_cstring(s, temp_allocator()) +} + +@(require_results) +string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) { + s := string(b) + i := 0 + for ; i < len(s); i += 1 { + if s[i] == 0 { + break + } + } + return s[:i] +} + +@(require_results) +concatenate_strings_from_buffer :: proc(buf: []byte, strings: ..string) -> string { + n := 0 + for s in strings { + (n < len(buf)) or_break + n += copy(buf[n:], s) + } + n = min(len(buf), n) + return string(buf[:n]) +} + +@(require_results) +concatenate :: proc(strings: []string, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + n := 0 + for s in strings { + n += len(s) + } + buf := make([]byte, n) or_return + n = 0 + for s in strings { + n += copy(buf[n:], s) + } + return string(buf), nil +} + + + +@(private="file") +random_string_seed: [2]u64 + +@(init, private="file") +init_random_string_seed :: proc() { + seed := u64(intrinsics.read_cycle_counter()) + s := &random_string_seed + s[0] = 0 + s[1] = (seed << 1) | 1 + _ = next_random(s) + s[1] += seed + _ = next_random(s) +} + +@(require_results) +next_random :: proc(r: ^[2]u64) -> u64 { + old_state := r[0] + r[0] = old_state * 6364136223846793005 + (r[1]|1) + xor_shifted := (((old_state >> 59) + 5) ~ old_state) * 12605985483714917081 + rot := (old_state >> 59) + return (xor_shifted >> rot) | (xor_shifted << ((-rot) & 63)) +} + +@(require_results) +random_string :: proc(buf: []byte) -> string { + @(static, rodata) digits := "0123456789" + + u := next_random(&random_string_seed) + + b :: 10 + i := len(buf) + for u >= b { + i -= 1 + buf[i] = digits[u % b] + u /= b + } + i -= 1 + buf[i] = digits[u % b] + return string(buf[i:]) +} diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index a3e7a5a96..f3c662a70 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -2,20 +2,24 @@ package os2 import "base:runtime" -Path_Separator :: _Path_Separator // OS-Specific -Path_List_Separator :: _Path_List_Separator // OS-Specific +Path_Separator :: _Path_Separator // OS-Specific +Path_Separator_String :: _Path_Separator_String // OS-Specific +Path_List_Separator :: _Path_List_Separator // OS-Specific +@(require_results) is_path_separator :: proc(c: byte) -> bool { return _is_path_separator(c) } mkdir :: make_directory -make_directory :: proc(name: string, perm: File_Mode) -> Error { + +make_directory :: proc(name: string, perm: int) -> Error { return _mkdir(name, perm) } mkdir_all :: make_directory_all -make_directory_all :: proc(path: string, perm: File_Mode) -> Error { + +make_directory_all :: proc(path: string, perm: int) -> Error { return _mkdir_all(path, perm) } @@ -23,13 +27,15 @@ remove_all :: proc(path: string) -> Error { return _remove_all(path) } - getwd :: get_working_directory + +@(require_results) get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { return _getwd(allocator) } setwd :: set_working_directory + set_working_directory :: proc(dir: string) -> (err: Error) { return _setwd(dir) } diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 93de749b8..80d4d42d1 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -1,103 +1,79 @@ //+private package os2 -import "core:strings" import "core:strconv" import "base:runtime" -import "core:sys/unix" +import "core:sys/linux" -_Path_Separator :: '/' -_Path_List_Separator :: ':' +_Path_Separator :: '/' +_Path_Separator_String :: "/" +_Path_List_Separator :: ':' -_S_IFMT :: 0o170000 // Type of file mask -_S_IFIFO :: 0o010000 // Named pipe (fifo) -_S_IFCHR :: 0o020000 // Character special -_S_IFDIR :: 0o040000 // Directory -_S_IFBLK :: 0o060000 // Block special -_S_IFREG :: 0o100000 // Regular -_S_IFLNK :: 0o120000 // Symbolic link -_S_IFSOCK :: 0o140000 // Socket - -_OPENDIR_FLAGS :: _O_RDONLY|_O_NONBLOCK|_O_DIRECTORY|_O_LARGEFILE|_O_CLOEXEC +_OPENDIR_FLAGS : linux.Open_Flags : {.NONBLOCK, .DIRECTORY, .LARGEFILE, .CLOEXEC} _is_path_separator :: proc(c: byte) -> bool { return c == '/' } -_mkdir :: proc(path: string, perm: File_Mode) -> Error { - // NOTE: These modes would require sys_mknod, however, that would require - // additional arguments to this function. - if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { - return .Invalid_Argument - } - - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - return _ok_or_error(unix.sys_mkdir(path_cstr, uint(perm & 0o777))) +_mkdir :: proc(path: string, perm: int) -> Error { + TEMP_ALLOCATOR_GUARD() + path_cstr := temp_cstring(path) or_return + return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm))) } -_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { - _mkdirat :: proc(dfd: int, path: []u8, perm: int, has_created: ^bool) -> Error { - if len(path) == 0 { - return _ok_or_error(unix.sys_close(dfd)) - } +_mkdir_all :: proc(path: string, perm: int) -> Error { + mkdirat :: proc(dfd: linux.Fd, path: []u8, perm: int, has_created: ^bool) -> Error { i: int - for /**/; i < len(path) - 1 && path[i] != '/'; i += 1 {} + for ; i < len(path) - 1 && path[i] != '/'; i += 1 {} + if i == 0 { + return _get_platform_error(linux.close(dfd)) + } path[i] = 0 - new_dfd := unix.sys_openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS) - switch new_dfd { - case -ENOENT: - if res := unix.sys_mkdirat(dfd, cstring(&path[0]), uint(perm)); res < 0 { - return _get_platform_error(res) + new_dfd, errno := linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS) + #partial switch errno { + case .ENOENT: + if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)u32(perm)); errno != .NONE { + return _get_platform_error(errno) } has_created^ = true - if new_dfd = unix.sys_openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); new_dfd < 0 { - return _get_platform_error(new_dfd) + if new_dfd, errno = linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); errno != .NONE { + return _get_platform_error(errno) } fallthrough - case 0: - if res := unix.sys_close(dfd); res < 0 { - return _get_platform_error(res) + case .NONE: + if errno = linux.close(dfd); errno != .NONE { + return _get_platform_error(errno) } // skip consecutive '/' for i += 1; i < len(path) && path[i] == '/'; i += 1 {} - return _mkdirat(new_dfd, path[i:], perm, has_created) + return mkdirat(new_dfd, path[i:], perm, has_created) case: - return _get_platform_error(new_dfd) + return _get_platform_error(errno) } unreachable() } - - if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { - return .Invalid_Argument - } - + TEMP_ALLOCATOR_GUARD() // need something we can edit, and use to generate cstrings - allocated: bool - path_bytes: []u8 - if len(path) > _CSTRING_NAME_HEAP_THRESHOLD { - allocated = true - path_bytes = make([]u8, len(path) + 1) - } else { - path_bytes = make([]u8, len(path) + 1, context.temp_allocator) - } + path_bytes := make([]u8, len(path) + 1, temp_allocator()) - // NULL terminate the byte slice to make it a valid cstring + // zero terminate the byte slice to make it a valid cstring copy(path_bytes, path) path_bytes[len(path)] = 0 - dfd: int + dfd: linux.Fd + errno: linux.Errno if path_bytes[0] == '/' { - dfd = unix.sys_open("/", _OPENDIR_FLAGS) + dfd, errno = linux.open("/", _OPENDIR_FLAGS) path_bytes = path_bytes[1:] } else { - dfd = unix.sys_open(".", _OPENDIR_FLAGS) + dfd, errno = linux.open(".", _OPENDIR_FLAGS) } - if dfd < 0 { - return _get_platform_error(dfd) + if errno != .NONE { + return _get_platform_error(errno) } has_created: bool - _mkdirat(dfd, path_bytes, int(perm & 0o777), &has_created) or_return + mkdirat(dfd, path_bytes, perm, &has_created) or_return if has_created { return nil } @@ -116,28 +92,28 @@ dirent64 :: struct { _remove_all :: proc(path: string) -> Error { DT_DIR :: 4 - _remove_all_dir :: proc(dfd: int) -> Error { + remove_all_dir :: proc(dfd: linux.Fd) -> Error { n := 64 buf := make([]u8, n) defer delete(buf) loop: for { - getdents_res := unix.sys_getdents64(dfd, &buf[0], n) - switch getdents_res { - case -EINVAL: + buflen, errno := linux.getdents(dfd, buf[:]) + #partial switch errno { + case .EINVAL: delete(buf) n *= 2 buf = make([]u8, n) continue loop - case -4096..<0: - return _get_platform_error(getdents_res) - case 0: - break loop + case .NONE: + if buflen == 0 { break loop } + case: + return _get_platform_error(errno) } d: ^dirent64 - for i := 0; i < getdents_res; i += int(d.d_reclen) { + for i := 0; i < buflen; i += int(d.d_reclen) { d = (^dirent64)(rawptr(&buf[i])) d_name_cstr := cstring(&d.d_name[0]) @@ -153,42 +129,44 @@ _remove_all :: proc(path: string) -> Error { continue } - unlink_res: int - switch d.d_type { case DT_DIR: - new_dfd := unix.sys_openat(dfd, d_name_cstr, _OPENDIR_FLAGS) - if new_dfd < 0 { - return _get_platform_error(new_dfd) + new_dfd: linux.Fd + new_dfd, errno = linux.openat(dfd, d_name_cstr, _OPENDIR_FLAGS) + if errno != .NONE { + return _get_platform_error(errno) } - defer unix.sys_close(new_dfd) - _remove_all_dir(new_dfd) or_return - unlink_res = unix.sys_unlinkat(dfd, d_name_cstr, int(unix.AT_REMOVEDIR)) + defer linux.close(new_dfd) + remove_all_dir(new_dfd) or_return + errno = linux.unlinkat(dfd, d_name_cstr, {.REMOVEDIR}) case: - unlink_res = unix.sys_unlinkat(dfd, d_name_cstr) + errno = linux.unlinkat(dfd, d_name_cstr, nil) } - if unlink_res < 0 { - return _get_platform_error(unlink_res) + if errno != .NONE { + return _get_platform_error(errno) } } } return nil } - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + TEMP_ALLOCATOR_GUARD() + path_cstr := temp_cstring(path) or_return - fd := unix.sys_open(path_cstr, _OPENDIR_FLAGS) - switch fd { - case -ENOTDIR: - return _ok_or_error(unix.sys_unlink(path_cstr)) - case -4096..<0: - return _get_platform_error(fd) + fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS) + #partial switch errno { + case .NONE: + break + case .ENOTDIR: + return _get_platform_error(linux.unlink(path_cstr)) + case: + return _get_platform_error(errno) } - defer unix.sys_close(fd) - _remove_all_dir(fd) or_return - return _ok_or_error(unix.sys_rmdir(path_cstr)) + defer linux.close(fd) + remove_all_dir(fd) or_return + return _get_platform_error(linux.rmdir(path_cstr)) } _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { @@ -199,13 +177,12 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { PATH_MAX :: 4096 buf := make([dynamic]u8, PATH_MAX, allocator) for { - #no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf))) - - if res >= 0 { - return strings.string_from_null_terminated_ptr(&buf[0], len(buf)), nil + #no_bounds_check n, errno := linux.getcwd(buf[:]) + if errno == .NONE { + return string(buf[:n-1]), nil } - if res != -ERANGE { - return "", _get_platform_error(res) + if errno != .ERANGE { + return "", _get_platform_error(errno) } resize(&buf, len(buf)+PATH_MAX) } @@ -213,17 +190,17 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { } _setwd :: proc(dir: string) -> Error { - dir_cstr := strings.clone_to_cstring(dir, context.temp_allocator) - return _ok_or_error(unix.sys_chdir(dir_cstr)) + dir_cstr := temp_cstring(dir) or_return + return _get_platform_error(linux.chdir(dir_cstr)) } -_get_full_path :: proc(fd: int, allocator: runtime.Allocator) -> string { +_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> string { PROC_FD_PATH :: "/proc/self/fd/" buf: [32]u8 copy(buf[:], PROC_FD_PATH) - strconv.itoa(buf[len(PROC_FD_PATH):], fd) + strconv.itoa(buf[len(PROC_FD_PATH):], int(fd)) fullpath: string err: Error @@ -232,4 +209,3 @@ _get_full_path :: proc(fd: int, allocator: runtime.Allocator) -> string { } return fullpath } - diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index 7be4696d7..ab680cc4c 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -3,27 +3,27 @@ package os2 import win32 "core:sys/windows" import "base:runtime" -import "core:strings" -_Path_Separator :: '\\' -_Path_List_Separator :: ';' +_Path_Separator :: '\\' +_Path_Separator_String :: "\\" +_Path_List_Separator :: ';' _is_path_separator :: proc(c: byte) -> bool { return c == '\\' || c == '/' } -_mkdir :: proc(name: string, perm: File_Mode) -> Error { +_mkdir :: proc(name: string, perm: int) -> Error { if !win32.CreateDirectoryW(_fix_long_path(name), nil) { return _get_platform_error() } return nil } -_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { +_mkdir_all :: proc(path: string, perm: int) -> Error { fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) { if len(p) == len(`\\?\c:`) { if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' { - s = strings.concatenate({p, `\`}, _file_allocator()) or_return + s = concatenate({p, `\`}, file_allocator()) or_return allocated = true return } @@ -31,11 +31,11 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { return p, false, nil } - _TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD() - dir, err := stat(path, _temp_allocator()) + dir_stat, err := stat(path, temp_allocator()) if err == nil { - if dir.is_directory { + if dir_stat.type == .Directory { return nil } return .Exist @@ -54,15 +54,15 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { if j > 1 { new_path, allocated := fix_root_directory(path[:j-1]) or_return defer if allocated { - delete(new_path, _file_allocator()) + delete(new_path, file_allocator()) } mkdir_all(new_path, perm) or_return } err = mkdir(path, perm) if err != nil { - dir1, err1 := lstat(path, _temp_allocator()) - if err1 == nil && dir1.is_directory { + new_dir_stat, err1 := lstat(path, temp_allocator()) + if err1 == nil && new_dir_stat.type == .Directory { return nil } return err @@ -85,7 +85,6 @@ _setwd :: proc(dir: string) -> (err: Error) { return nil } - can_use_long_paths: bool @(init) @@ -96,7 +95,6 @@ init_long_path_support :: proc() { can_use_long_paths = false } - _fix_long_path_slice :: proc(path: string) -> []u16 { return win32.utf8_to_utf16(_fix_long_path_internal(path)) } @@ -105,7 +103,6 @@ _fix_long_path :: proc(path: string) -> win32.wstring { return win32.utf8_to_wstring(_fix_long_path_internal(path)) } - _fix_long_path_internal :: proc(path: string) -> string { if can_use_long_paths { return path @@ -127,10 +124,10 @@ _fix_long_path_internal :: proc(path: string) -> string { return path } - _TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD() PREFIX :: `\\?` - path_buf := make([]byte, len(PREFIX)+len(path)+1, _temp_allocator()) + path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator()) copy(path_buf, PREFIX) n := len(path) r, w := 0, len(PREFIX) @@ -162,5 +159,4 @@ _fix_long_path_internal :: proc(path: string) -> string { } return string(path_buf[:w]) - } diff --git a/core/os/os2/pipe.odin b/core/os/os2/pipe.odin index 62f7ddf10..9254d6f8e 100644 --- a/core/os/os2/pipe.odin +++ b/core/os/os2/pipe.odin @@ -1,5 +1,6 @@ package os2 +@(require_results) pipe :: proc() -> (r, w: ^File, err: Error) { return _pipe() } diff --git a/core/os/os2/pipe_linux.odin b/core/os/os2/pipe_linux.odin index b66ff9663..8835cc30f 100644 --- a/core/os/os2/pipe_linux.odin +++ b/core/os/os2/pipe_linux.odin @@ -1,7 +1,17 @@ //+private package os2 +import "core:sys/linux" + _pipe :: proc() -> (r, w: ^File, err: Error) { - return nil, nil, nil + fds: [2]linux.Fd + errno := linux.pipe2(&fds, {}) + if errno != .NONE { + return nil, nil,_get_platform_error(errno) + } + + r = _new_file(uintptr(fds[0])) + w = _new_file(uintptr(fds[1])) + return } diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin index bab8b44f5..59615e306 100644 --- a/core/os/os2/pipe_windows.odin +++ b/core/os/os2/pipe_windows.odin @@ -5,7 +5,11 @@ import win32 "core:sys/windows" _pipe :: proc() -> (r, w: ^File, err: Error) { p: [2]win32.HANDLE - if !win32.CreatePipe(&p[0], &p[1], nil, 0) { + sa := win32.SECURITY_ATTRIBUTES { + nLength = size_of(win32.SECURITY_ATTRIBUTES), + bInheritHandle = true, + } + if !win32.CreatePipe(&p[0], &p[1], &sa, 0) { return nil, nil, _get_platform_error() } return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 862434b7b..3f3e64668 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -1,102 +1,406 @@ package os2 -import "core:sync" -import "core:time" import "base:runtime" +import "core:time" -args: []string +/* + In procedures that explicitly state this as one of the allowed values, + specifies an infinite timeout. +*/ +TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration will be treated as infinity +/* + Arguments to the current process. +*/ +args := get_args() + +@(private="file", require_results) +get_args :: proc() -> []string { + result := make([]string, len(runtime.args__), heap_allocator()) + for rt_arg, i in runtime.args__ { + result[i] = string(rt_arg) + } + return result +} + +/* + Exit the current process. +*/ exit :: proc "contextless" (code: int) -> ! { - runtime.trap() + _exit(code) } +/* + Obtain the UID of the current process. + + **Note(windows)**: Windows doesn't follow the posix permissions model, so + the function simply returns -1. +*/ +@(require_results) get_uid :: proc() -> int { - return -1 + return _get_uid() } +/* + Obtain the effective UID of the current process. + + The effective UID is typically the same as the UID of the process. In case + the process was run by a user with elevated permissions, the process may + lower the privilege to perform some tasks without privilege. In these cases + the real UID of the process and the effective UID are different. + + **Note(windows)**: Windows doesn't follow the posix permissions model, so + the function simply returns -1. +*/ +@(require_results) get_euid :: proc() -> int { - return -1 + return _get_euid() } +/* + Obtain the GID of the current process. + + **Note(windows)**: Windows doesn't follow the posix permissions model, so + the function simply returns -1. +*/ +@(require_results) get_gid :: proc() -> int { - return -1 + return _get_gid() } +/* + Obtain the effective GID of the current process. + + The effective GID is typically the same as the GID of the process. In case + the process was run by a user with elevated permissions, the process may + lower the privilege to perform some tasks without privilege. In these cases + the real GID of the process and the effective GID are different. + + **Note(windows)**: Windows doesn't follow the posix permissions model, so + the function simply returns -1. +*/ +@(require_results) get_egid :: proc() -> int { - return -1 + return _get_egid() } +/* + Obtain the ID of the current process. +*/ +@(require_results) get_pid :: proc() -> int { - return -1 + return _get_pid() } +/* + Obtain the ID of the parent process. + + **Note(windows)**: Windows does not mantain strong relationships between + parent and child processes. This function returns the ID of the process + that has created the current process. In case the parent has died, the ID + returned by this function can identify a non-existent or a different + process. +*/ +@(require_results) get_ppid :: proc() -> int { - return -1 + return _get_ppid() } +/* + Obtain ID's of all processes running in the system. +*/ +@(require_results) +process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { + return _process_list(allocator) +} +/* + Bit set specifying which fields of the `Process_Info` struct need to be + obtained by the `process_info()` procedure. Each bit corresponds to a + field in the `Process_Info` struct. +*/ +Process_Info_Fields :: bit_set[Process_Info_Field] +Process_Info_Field :: enum { + Executable_Path, + PPid, + Priority, + Command_Line, + Command_Args, + Environment, + Username, + Working_Dir, +} + +/* + Contains information about the process as obtained by the `process_info()` + procedure. +*/ +Process_Info :: struct { + // The information about a process the struct contains. `pid` is always + // stored, no matter what. + fields: Process_Info_Fields, + // The ID of the process. + pid: int, + // The ID of the parent process. + ppid: int, + // The process priority. + priority: int, + // The path to the executable, which the process runs. + executable_path: string, + // The command line supplied to the process. + command_line: string, + // The arguments supplied to the process. + command_args: []string, + // The environment of the process. + environment: []string, + // The username of the user who started the process. + username: string, + // The current working directory of the process. + working_dir: string, +} + +/* + Obtain information about a process. + + This procedure obtains an information, specified by `selection` parameter of + a process given by `pid`. + + Use `free_process_info` to free the memory allocated by this procedure. In + case the function returns an error all temporary allocations would be freed + and as such, calling `free_process_info()` is not needed. + + **Note**: The resulting information may or may contain the fields specified + by the `selection` parameter. Always check whether the returned + `Process_Info` struct has the required fields before checking the error code + returned by this function. +*/ +@(require_results) +process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { + return _process_info_by_pid(pid, selection, allocator) +} + +/* + Obtain information about a process. + + This procedure obtains information, specified by `selection` parameter + about a process that has been opened by the application, specified in + the `process` parameter. + + Use `free_process_info` to free the memory allocated by this procedure. In + case the function returns an error, all temporary allocations would be freed + and as such, calling `free_process_info` is not needed. + + **Note**: The resulting information may or may contain the fields specified + by the `selection` parameter. Always check whether the returned + `Process_Info` struct has the required fields before checking the error code + returned by this function. +*/ +@(require_results) +process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { + return _process_info_by_handle(process, selection, allocator) +} + +/* + Obtain information about the current process. + + This procedure obtains the information, specified by `selection` parameter + about the currently running process. + + Use `free_process_info` to free the memory allocated by this function. In + case this function returns an error, all temporary allocations would be + freed and as such calling `free_process_info()` is not needed. + + **Note**: The resulting information may or may contain the fields specified + by the `selection` parameter. Always check whether the returned + `Process_Info` struct has the required fields before checking the error code + returned by this function. +*/ +@(require_results) +current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { + return _current_process_info(selection, allocator) +} + +/* + Obtain information about the specified process. +*/ +process_info :: proc { + process_info_by_pid, + process_info_by_handle, + current_process_info, +} + +/* + Free the information about the process. + + This procedure frees the memory occupied by process info using the provided + allocator. The allocator needs to be the same allocator that was supplied + to the `process_info` function. +*/ +free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) { + delete(pi.executable_path, allocator) + delete(pi.command_line, allocator) + delete(pi.command_args, allocator) + for s in pi.environment { + delete(s, allocator) + } + delete(pi.environment, allocator) + delete(pi.working_dir, allocator) +} + +/* + Represents a process handle. + + When a process dies, the OS is free to re-use the pid of that process. The + `Process` struct represents a handle to the process that will refer to a + specific process, even after it has died. + + **Note(linux)**: The `handle` will be referring to pidfd. +*/ Process :: struct { - pid: int, - handle: uintptr, - is_done: b32, - signal_mutex: sync.RW_Mutex, + pid: int, + handle: uintptr, } +Process_Open_Flags :: bit_set[Process_Open_Flag] +Process_Open_Flag :: enum { + // Request for reading from the virtual memory of another process. + Mem_Read, + // Request for writing to the virtual memory of another process. + Mem_Write, +} -Process_Attributes :: struct { - dir: string, +/* + Open a process handle using it's pid. + + This procedure obtains a process handle of a process specified by `pid`. + This procedure can be subject to race conditions. See the description of + `Process`. + + Use `process_close()` function to close the process handle. +*/ +@(require_results) +process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Error) { + return _process_open(pid, flags) +} + +/* + The description of how a process should be created. +*/ +Process_Desc :: struct { + // OS-specific attributes. + sys_attr: _Sys_Process_Attributes, + // The working directory of the process. If the string has length 0, the + // working directory is assumed to be the current working directory of the + // current process. + working_dir: string, + // The command to run. Each element of the slice is a separate argument to + // the process. The first element of the slice would be the executable. + command: []string, + // A slice of strings, each having the format `KEY=VALUE` representing the + // full environment that the child process will receive. + // In case this slice is `nil`, the current process' environment is used. env: []string, - files: []^File, - sys: ^Process_Attributes_OS_Specific, + // The `stderr` handle to give to the child process. It can be either a file + // or a writeable end of a pipe. Passing `nil` will shut down the process' + // stderr output. + stderr: ^File, + // The `stdout` handle to give to the child process. It can be either a file + // or a writeabe end of a pipe. Passing a `nil` will shut down the process' + // stdout output. + stdout: ^File, + // The `stdin` handle to give to the child process. It can either be a file + // or a readable end of a pipe. Passing a `nil` will shut down the process' + // input. + stdin: ^File, } -Process_Attributes_OS_Specific :: struct{} +/* + Create a new process and obtain its handle. -Process_Error :: enum { - None, + This procedure creates a new process, with a given command and environment + strings as parameters. Use `environ()` to inherit the environment of the + current process. + + The `desc` parameter specifies the description of how the process should + be created. It contains information such as the command line, the + environment of the process, the starting directory and many other options. + Most of the fields in the struct can be set to `nil` or an empty value. + + Use `process_close` to close the handle to the process. Note, that this + is not the same as terminating the process. One can terminate the process + and not close the handle, in which case the handle would be leaked. In case + the function returns an error, an invalid handle is returned. + + This procedure is not thread-safe. It may alter the inheritance properties + of file handles in an unpredictable manner. In case multiple threads change + handle inheritance properties, make sure to serialize all those calls. +*/ +@(require_results) +process_start :: proc(desc := Process_Desc {}) -> (Process, Error) { + return _process_start(desc) } +/* + The state of the process after it has finished execution. +*/ Process_State :: struct { - pid: int, - exit_code: int, - exited: bool, - success: bool, + // The ID of the process. + pid: int, + // Specifies whether the process has terminated or is still running. + exited: bool, + // The exit code of the process, if it has exited. + // Will also store the number of the exception or signal that has crashed the + // process. + exit_code: int, + // Specifies whether the termination of the process was successfull or not, + // i.e. whether it has crashed or not. + // **Note(windows)**: On windows `true` is always returned, as there is no + // reliable way to obtain information about whether the process has crashed. + success: bool, + // The time the process has spend executing in kernel time. system_time: time.Duration, - user_time: time.Duration, - sys: rawptr, + // The time the process has spend executing in userspace. + user_time: time.Duration, } -Signal :: #type proc() +/* + Wait for a process event. -Kill: Signal = nil -Interrupt: Signal = nil + This procedure blocks the execution until the process has exited or the + timeout (if specified) has reached zero. If the timeout is `TIMEOUT_INFINITE`, + no timeout restriction is imposed and the procedure can block indefinately. + If the timeout has expired, the `General_Error.Timeout` is returned as + the error. -find_process :: proc(pid: int) -> (^Process, Process_Error) { - return nil, .None + If an error is returned for any other reason, other than timeout, the + process state is considered undetermined. +*/ +@(require_results) +process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_State, Error) { + return _process_wait(process, timeout) } +/* + Close the handle to a process. -process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) { - return nil, .None + This procedure closes the handle associated with a process. It **does not** + terminate a process, in case it was running. In case a termination is + desired, kill the process first, wait for the process to finish, + then close the handle. +*/ +@(require_results) +process_close :: proc(process: Process) -> (Error) { + return _process_close(process) } -process_release :: proc(p: ^Process) -> Process_Error { - return .None +/* + Terminate a process. + + This procedure terminates a process, specified by it's handle, `process`. + +*/ +@(require_results) +process_kill :: proc(process: Process) -> (Error) { + return _process_kill(process) } - -process_kill :: proc(p: ^Process) -> Process_Error { - return .None -} - -process_signal :: proc(p: ^Process, sig: Signal) -> Process_Error { - return .None -} - -process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) { - return {}, .None -} - - - - diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin new file mode 100644 index 000000000..8d61e7be7 --- /dev/null +++ b/core/os/os2/process_windows.odin @@ -0,0 +1,696 @@ +//+build windows +//+private file +package os2 + +import "base:runtime" + +import "core:strings" +import win32 "core:sys/windows" +import "core:time" + +@(private="package") +_exit :: proc "contextless" (code: int) -> ! { + win32.ExitProcess(u32(code)) +} + +@(private="package") +_get_uid :: proc() -> int { + return -1 +} + +@(private="package") +_get_euid :: proc() -> int { + return -1 +} + +@(private="package") +_get_gid :: proc() -> int { + return -1 +} + +@(private="package") +_get_egid :: proc() -> int { + return -1 +} + +@(private="package") +_get_pid :: proc() -> int { + return int(win32.GetCurrentProcessId()) +} + +@(private="package") +_get_ppid :: proc() -> int { + our_pid := win32.GetCurrentProcessId() + snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) + if snap == win32.INVALID_HANDLE_VALUE { + return -1 + } + defer win32.CloseHandle(snap) + entry := win32.PROCESSENTRY32W { dwSize = size_of(win32.PROCESSENTRY32W) } + for status := win32.Process32FirstW(snap, &entry); status; /**/ { + if entry.th32ProcessID == our_pid { + return int(entry.th32ParentProcessID) + } + status = win32.Process32NextW(snap, &entry) + } + return -1 +} + +@(private="package") +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) + if snap == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } + + list_d := make([dynamic]int, allocator) or_return + + entry := win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)} + status := win32.Process32FirstW(snap, &entry) + for status { + append(&list_d, int(entry.th32ProcessID)) + status = win32.Process32NextW(snap, &entry) + } + list = list_d[:] + return +} + +@(require_results) +read_memory_as_struct :: proc(h: win32.HANDLE, addr: rawptr, dest: ^$T) -> (bytes_read: uint, err: Error) { + if !win32.ReadProcessMemory(h, addr, dest, size_of(T), &bytes_read) { + err = _get_platform_error() + } + return +} +@(require_results) +read_memory_as_slice :: proc(h: win32.HANDLE, addr: rawptr, dest: []$T) -> (bytes_read: uint, err: Error) { + if !win32.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), &bytes_read) { + err = _get_platform_error() + } + return +} + +@(private="package") +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + info.pid = pid + defer if err != nil { + free_process_info(info, allocator) + } + + // Data obtained from process snapshots + if selection >= {.PPid, .Priority} { + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { + err = General_Error.Not_Exist + return + } + if .PPid in selection { + info.fields += {.PPid} + info.ppid = int(entry.th32ParentProcessID) + } + if .Priority in selection { + info.fields += {.Priority} + info.priority = int(entry.pcPriClassBase) + } + } + if .Executable_Path in selection { // snap module + info.executable_path = _process_exe_by_pid(pid, allocator) or_return + info.fields += {.Executable_Path} + } + + ph := win32.INVALID_HANDLE_VALUE + + if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { // need process handle + ph = win32.OpenProcess( + win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ, + false, + u32(pid), + ) + if ph == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } + } + defer if ph != win32.INVALID_HANDLE_VALUE { + win32.CloseHandle(ph) + } + + if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb + process_info_size: u32 + process_info: win32.PROCESS_BASIC_INFORMATION + status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) + if status != 0 { + // TODO(flysand): There's probably a mismatch between NTSTATUS and + // windows userland error codes, I haven't checked. + err = Platform_Error(status) + return + } + if process_info.PebBaseAddress == nil { + // Not sure what the error is + err = General_Error.Unsupported + return + } + process_peb: win32.PEB + + _ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return + + process_params: win32.RTL_USER_PROCESS_PARAMETERS + _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return + + if selection >= {.Command_Line, .Command_Args} { + TEMP_ALLOCATOR_GUARD() + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return + _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return + + if .Command_Line in selection { + info.command_line = win32.utf16_to_utf8(cmdline_w, allocator) or_return + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return + info.fields += {.Command_Args} + } + } + if .Environment in selection { + TEMP_ALLOCATOR_GUARD() + env_len := process_params.EnvironmentSize / 2 + envs_w := make([]u16, env_len, temp_allocator()) or_return + _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return + + info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return + info.fields += {.Environment} + } + if .Working_Dir in selection { + TEMP_ALLOCATOR_GUARD() + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return + _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return + + info.working_dir = win32.utf16_to_utf8(cwd_w, allocator) or_return + info.fields += {.Working_Dir} + } + } + + if .Username in selection { + info.username = _get_process_user(ph, allocator) or_return + info.fields += {.Username} + } + err = nil + return +} + +@(private="package") +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + pid := process.pid + info.pid = pid + defer if err != nil { + free_process_info(info, allocator) + } + + // Data obtained from process snapshots + if selection >= {.PPid, .Priority} { // snap process + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { + err = General_Error.Not_Exist + return + } + if .PPid in selection { + info.fields += {.PPid} + info.ppid = int(entry.th32ParentProcessID) + } + if .Priority in selection { + info.fields += {.Priority} + info.priority = int(entry.pcPriClassBase) + } + } + if .Executable_Path in selection { // snap module + info.executable_path = _process_exe_by_pid(pid, allocator) or_return + info.fields += {.Executable_Path} + } + ph := win32.HANDLE(process.handle) + if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb + process_info_size: u32 + process_info: win32.PROCESS_BASIC_INFORMATION + status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) + if status != 0 { + // TODO(flysand): There's probably a mismatch between NTSTATUS and + // windows userland error codes, I haven't checked. + err = Platform_Error(status) + return + } + if process_info.PebBaseAddress == nil { + // Not sure what the error is + err = General_Error.Unsupported + return + } + + process_peb: win32.PEB + _ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return + + process_params: win32.RTL_USER_PROCESS_PARAMETERS + _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return + + if selection >= {.Command_Line, .Command_Args} { + TEMP_ALLOCATOR_GUARD() + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return + _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return + + if .Command_Line in selection { + info.command_line = win32.utf16_to_utf8(cmdline_w, allocator) or_return + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return + info.fields += {.Command_Args} + } + } + + if .Environment in selection { + TEMP_ALLOCATOR_GUARD() + env_len := process_params.EnvironmentSize / 2 + envs_w := make([]u16, env_len, temp_allocator()) or_return + _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return + + info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return + info.fields += {.Environment} + } + + if .Working_Dir in selection { + TEMP_ALLOCATOR_GUARD() + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return + _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return + + info.working_dir = win32.utf16_to_utf8(cwd_w, allocator) or_return + info.fields += {.Working_Dir} + } + } + if .Username in selection { + info.username = _get_process_user(ph, allocator) or_return + info.fields += {.Username} + } + err = nil + return +} + +@(private="package") +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + info.pid = get_pid() + defer if err != nil { + free_process_info(info, allocator) + } + + if selection >= {.PPid, .Priority} { // snap process + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { + err = General_Error.Not_Exist + return + } + if .PPid in selection { + info.fields += {.PPid} + info.ppid = int(entry.th32ProcessID) + } + if .Priority in selection { + info.fields += {.Priority} + info.priority = int(entry.pcPriClassBase) + } + } + if .Executable_Path in selection { + exe_filename_w: [256]u16 + path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w)) + info.executable_path = win32.utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return + info.fields += {.Executable_Path} + } + if selection >= {.Command_Line, .Command_Args} { + command_line_w := win32.GetCommandLineW() + if .Command_Line in selection { + info.command_line = win32.wstring_to_utf8(command_line_w, -1, allocator) or_return + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = _parse_command_line(command_line_w, allocator) or_return + info.fields += {.Command_Args} + } + } + if .Environment in selection { + env_block := win32.GetEnvironmentStringsW() + info.environment = _parse_environment_block(env_block, allocator) or_return + info.fields += {.Environment} + } + if .Username in selection { + process_handle := win32.GetCurrentProcess() + info.username = _get_process_user(process_handle, allocator) or_return + info.fields += {.Username} + } + if .Working_Dir in selection { + // TODO(flysand): Implement this by reading PEB + err = .Mode_Not_Implemented + return + } + err = nil + return +} + +@(private="package") +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + // Note(flysand): The handle will be used for querying information so we + // take the necessary permissions right away. + dwDesiredAccess := win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.SYNCHRONIZE + if .Mem_Read in flags { + dwDesiredAccess |= win32.PROCESS_VM_READ + } + if .Mem_Write in flags { + dwDesiredAccess |= win32.PROCESS_VM_WRITE + } + handle := win32.OpenProcess( + dwDesiredAccess, + false, + u32(pid), + ) + if handle == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + } else { + process = {pid = pid, handle = uintptr(handle)} + } + return +} + +@(private="package") +_Sys_Process_Attributes :: struct {} + +@(private="package") +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + TEMP_ALLOCATOR_GUARD() + command_line := _build_command_line(desc.command, temp_allocator()) + command_line_w := win32.utf8_to_wstring(command_line, temp_allocator()) + environment := desc.env + if desc.env == nil { + environment = environ(temp_allocator()) + } + environment_block := _build_environment_block(environment, temp_allocator()) + environment_block_w := win32.utf8_to_utf16(environment_block, temp_allocator()) + stderr_handle := win32.GetStdHandle(win32.STD_ERROR_HANDLE) + stdout_handle := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE) + stdin_handle := win32.GetStdHandle(win32.STD_INPUT_HANDLE) + + if desc.stdout != nil { + stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd) + } + if desc.stderr != nil { + stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) + } + if desc.stdin != nil { + stdin_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) + } + + working_dir_w := win32.utf8_to_wstring(desc.working_dir, temp_allocator()) if len(desc.working_dir) > 0 else nil + process_info: win32.PROCESS_INFORMATION + ok := win32.CreateProcessW( + nil, + command_line_w, + nil, + nil, + true, + win32.CREATE_UNICODE_ENVIRONMENT|win32.NORMAL_PRIORITY_CLASS, + raw_data(environment_block_w), + working_dir_w, + &win32.STARTUPINFOW{ + cb = size_of(win32.STARTUPINFOW), + hStdError = stderr_handle, + hStdOutput = stdout_handle, + hStdInput = stdin_handle, + dwFlags = win32.STARTF_USESTDHANDLES, + }, + &process_info, + ) + if !ok { + err = _get_platform_error() + return + } + process = {pid = int(process_info.dwProcessId), handle = uintptr(process_info.hProcess)} + return +} + +@(private="package") +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + handle := win32.HANDLE(process.handle) + timeout_ms := u32(timeout / time.Millisecond) if timeout >= 0 else win32.INFINITE + + switch win32.WaitForSingleObject(handle, timeout_ms) { + case win32.WAIT_OBJECT_0: + exit_code: u32 + if !win32.GetExitCodeProcess(handle, &exit_code) { + err =_get_platform_error() + return + } + time_created: win32.FILETIME + time_exited: win32.FILETIME + time_kernel: win32.FILETIME + time_user: win32.FILETIME + if !win32.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) { + err = _get_platform_error() + return + } + process_state = { + exit_code = int(exit_code), + exited = true, + pid = process.pid, + success = true, + system_time = _filetime_to_duration(time_kernel), + user_time = _filetime_to_duration(time_user), + } + return + case win32.WAIT_TIMEOUT: + err = General_Error.Timeout + return + case: + err = _get_platform_error() + return + } +} + +@(private="package") +_process_close :: proc(process: Process) -> Error { + if !win32.CloseHandle(win32.HANDLE(process.handle)) { + return _get_platform_error() + } + return nil +} + +@(private="package") +_process_kill :: proc(process: Process) -> Error { + // Note(flysand): This is different than what the task manager's "kill process" + // functionality does, as we don't try to send WM_CLOSE message first. This + // is quite a rough way to kill the process, which should be consistent with + // linux. The error code 9 is to mimic SIGKILL event. + if !win32.TerminateProcess(win32.HANDLE(process.handle), 9) { + return _get_platform_error() + } + return nil +} + +_filetime_to_duration :: proc(filetime: win32.FILETIME) -> time.Duration { + ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime) + return time.Duration(ticks * 100) +} + +_process_entry_by_pid :: proc(pid: int) -> (entry: win32.PROCESSENTRY32W, err: Error) { + snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) + if snap == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } + defer win32.CloseHandle(snap) + + entry = win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)} + status := win32.Process32FirstW(snap, &entry) + for status { + if u32(pid) == entry.th32ProcessID { + return + } + status = win32.Process32NextW(snap, &entry) + } + err = General_Error.Not_Exist + return +} + +// Note(flysand): Not sure which way it's better to get the executable path: +// via toolhelp snapshots or by reading other process' PEB memory. I have +// a slight suspicion that if both exe path and command line are desired, +// it's faster to just read both from PEB, but maybe the toolhelp snapshots +// are just better...? +@(private="package") +_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) { + snap := win32.CreateToolhelp32Snapshot( + win32.TH32CS_SNAPMODULE|win32.TH32CS_SNAPMODULE32, + u32(pid), + ) + if snap == win32.INVALID_HANDLE_VALUE { + err =_get_platform_error() + return + } + defer win32.CloseHandle(snap) + + entry := win32.MODULEENTRY32W { dwSize = size_of(win32.MODULEENTRY32W) } + status := win32.Module32FirstW(snap, &entry) + if !status { + err =_get_platform_error() + return + } + return win32.wstring_to_utf8(raw_data(entry.szExePath[:]), -1, allocator) +} + +_get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + token_handle: win32.HANDLE + if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) { + err = _get_platform_error() + return + } + token_user_size: u32 + if !win32.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) { + // Note(flysand): Make sure the buffer too small error comes out, and not any other error + err = _get_platform_error() + if v, ok := is_platform_error(err); !ok || v != i32(win32.ERROR_INSUFFICIENT_BUFFER) { + return + } + err = nil + } + token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return)) + if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) { + err = _get_platform_error() + return + } + + sid_type: win32.SID_NAME_USE + username_w: [256]u16 + domain_w: [256]u16 + username_chrs := u32(256) + domain_chrs := u32(256) + + if !win32.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) { + err = _get_platform_error() + return + } + username := win32.utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return + domain := win32.utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return + return strings.concatenate({domain, "\\", username}, allocator) +} + +_parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> (argv: []string, err: Error) { + argc: i32 + argv_w := win32.CommandLineToArgvW(cmd_line_w, &argc) + if argv_w == nil { + return nil, _get_platform_error() + } + argv = make([]string, argc, allocator) or_return + defer if err != nil { + for arg in argv { + delete(arg, allocator) + } + delete(argv, allocator) + } + for arg_w, i in argv_w[:argc] { + argv[i] = win32.wstring_to_utf8(arg_w, -1, allocator) or_return + } + return +} + +_build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> string { + _write_byte_n_times :: #force_inline proc(builder: ^strings.Builder, b: byte, n: int) { + for _ in 0 ..< n { + strings.write_byte(builder, b) + } + } + builder := strings.builder_make(allocator) + for arg, i in command { + if i != 0 { + strings.write_byte(&builder, ' ') + } + j := 0 + strings.write_byte(&builder, '"') + for j < len(arg) { + backslashes := 0 + for j < len(arg) && arg[j] == '\\' { + backslashes += 1 + j += 1 + } + if j == len(arg) { + _write_byte_n_times(&builder, '\\', 2*backslashes) + break + } else if arg[j] == '"' { + _write_byte_n_times(&builder, '\\', 2*backslashes+1) + strings.write_byte(&builder, '"') + } else { + _write_byte_n_times(&builder, '\\', backslashes) + strings.write_byte(&builder, arg[j]) + } + j += 1 + } + strings.write_byte(&builder, '"') + } + return strings.to_string(builder) +} + +_parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> (envs: []string, err: Error) { + zt_count := 0 + for idx := 0; true; { + if block[idx] == 0x0000 { + zt_count += 1 + if block[idx+1] == 0x0000 { + zt_count += 1 + break + } + } + idx += 1 + } + + // Note(flysand): Each string in the environment block is terminated + // by a NUL character. In addition, the environment block itself is + // terminated by a NUL character. So the number of strings in the + // environment block is the number of NUL character minus the + // block terminator. + env_count := zt_count - 1 + envs = make([]string, env_count, allocator) or_return + defer if err != nil { + for env in envs { + delete(env, allocator) + } + delete(envs, allocator) + } + + env_idx := 0 + last_idx := 0 + idx := 0 + for block[idx] != 0x0000 { + for block[idx] != 0x0000 { + idx += 1 + } + env_w := block[last_idx:idx] + envs[env_idx] = win32.utf16_to_utf8(env_w, allocator) or_return + env_idx += 1 + idx += 1 + last_idx = idx + } + return +} + +_build_environment_block :: proc(environment: []string, allocator: runtime.Allocator) -> string { + builder := strings.builder_make(allocator) + loop: #reverse for kv, cur_idx in environment { + eq_idx := strings.index_byte(kv, '=') + assert(eq_idx >= 0, "Malformed environment string. Expected '=' to separate keys and values") + key := kv[:eq_idx] + for old_kv in environment[cur_idx+1:] { + old_key := old_kv[:strings.index_byte(old_kv, '=')] + if key == old_key { + continue loop + } + } + strings.write_string(&builder, kv) + strings.write_byte(&builder, 0) + } + // Note(flysand): In addition to the NUL-terminator for each string, the + // environment block itself is NUL-terminated. + strings.write_byte(&builder, 0) + return strings.to_string(builder) +} diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index a64522ac1..fad33da0a 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -3,12 +3,14 @@ package os2 import "core:time" import "base:runtime" +Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) + File_Info :: struct { fullpath: string, name: string, size: i64, - mode: File_Mode, - is_directory: bool, + mode: int, + type: File_Type, creation_time: time.Time, modification_time: time.Time, access_time: time.Time, @@ -25,20 +27,30 @@ file_info_delete :: proc(fi: File_Info, allocator: runtime.Allocator) { delete(fi.fullpath, allocator) } +@(require_results) fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { - return _fstat(f, allocator) + if f == nil { + return {}, nil + } else if f.fstat != nil { + return f->fstat(allocator) + } + return {}, .Invalid_Callback } +@(require_results) stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return _stat(name, allocator) } lstat :: stat_do_not_follow_links + +@(require_results) stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return _lstat(name, allocator) } +@(require_results) same_file :: proc(fi1, fi2: File_Info) -> bool { return _same_file(fi1, fi2) } diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index db929a719..f194524a7 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -3,143 +3,72 @@ package os2 import "core:time" import "base:runtime" -import "core:strings" -import "core:sys/unix" +import "core:sys/linux" import "core:path/filepath" -// File type -S_IFMT :: 0o170000 // Type of file mask -S_IFIFO :: 0o010000 // Named pipe (fifo) -S_IFCHR :: 0o020000 // Character special -S_IFDIR :: 0o040000 // Directory -S_IFBLK :: 0o060000 // Block special -S_IFREG :: 0o100000 // Regular -S_IFLNK :: 0o120000 // Symbolic link -S_IFSOCK :: 0o140000 // Socket - -// File mode -// Read, write, execute/search by owner -S_IRWXU :: 0o0700 // RWX mask for owner -S_IRUSR :: 0o0400 // R for owner -S_IWUSR :: 0o0200 // W for owner -S_IXUSR :: 0o0100 // X for owner - - // Read, write, execute/search by group -S_IRWXG :: 0o0070 // RWX mask for group -S_IRGRP :: 0o0040 // R for group -S_IWGRP :: 0o0020 // W for group -S_IXGRP :: 0o0010 // X for group - - // Read, write, execute/search by others -S_IRWXO :: 0o0007 // RWX mask for other -S_IROTH :: 0o0004 // R for other -S_IWOTH :: 0o0002 // W for other -S_IXOTH :: 0o0001 // X for other - -S_ISUID :: 0o4000 // Set user id on execution -S_ISGID :: 0o2000 // Set group id on execution -S_ISVTX :: 0o1000 // Directory restrcted delete - - -S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } -S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } -S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } -S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } -S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } -S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } -S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } - -F_OK :: 0 // Test for file existance -X_OK :: 1 // Test for execute permission -W_OK :: 2 // Test for write permission -R_OK :: 4 // Test for read permission - -@private -Unix_File_Time :: struct { - seconds: i64, - nanoseconds: i64, -} - -@private -_Stat :: struct { - device_id: u64, // ID of device containing file - serial: u64, // File serial number - nlink: u64, // Number of hard links - mode: u32, // Mode of the file - uid: u32, // User ID of the file's owner - gid: u32, // Group ID of the file's group - _padding: i32, // 32 bits of padding - rdev: u64, // Device ID, if device - size: i64, // Size of the file, in bytes - block_size: i64, // Optimal bllocksize for I/O - blocks: i64, // Number of 512-byte blocks allocated - - last_access: Unix_File_Time, // Time of last access - modified: Unix_File_Time, // Time of last modification - status_change: Unix_File_Time, // Time of last status change - - _reserve1, - _reserve2, - _reserve3: i64, -} - - _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { - return _fstat_internal(f.impl.fd, allocator) + impl := (^File_Impl)(f.impl) + return _fstat_internal(impl.fd, allocator) } -_fstat_internal :: proc(fd: int, allocator: runtime.Allocator) -> (File_Info, Error) { - s: _Stat - result := unix.sys_fstat(fd, &s) - if result < 0 { - return {}, _get_platform_error(result) +_fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Info, Error) { + s: linux.Stat + errno := linux.fstat(fd, &s) + if errno != .NONE { + return {}, _get_platform_error(errno) } - + type := File_Type.Regular + switch s.mode & linux.S_IFMT { + case linux.S_IFBLK: type = .Block_Device + case linux.S_IFCHR: type = .Character_Device + case linux.S_IFDIR: type = .Directory + case linux.S_IFIFO: type = .Named_Pipe + case linux.S_IFLNK: type = .Symlink + case linux.S_IFREG: type = .Regular + case linux.S_IFSOCK: type = .Socket + } + mode := int(s.mode) & 0o7777 // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time fi := File_Info { fullpath = _get_full_path(fd, allocator), name = "", - size = s.size, - mode = 0, - is_directory = S_ISDIR(s.mode), - modification_time = time.Time {s.modified.seconds}, - access_time = time.Time {s.last_access.seconds}, - creation_time = time.Time{0}, // regular stat does not provide this + size = i64(s.size), + mode = mode, + type = type, + modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)}, + access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)}, + creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this } - + fi.creation_time = fi.modification_time fi.name = filepath.base(fi.fullpath) return fi, nil } // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath -_stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) +_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + TEMP_ALLOCATOR_GUARD() + name_cstr := temp_cstring(name) or_return - fd := unix.sys_open(name_cstr, _O_RDONLY) - if fd < 0 { - return {}, _get_platform_error(fd) + fd, errno := linux.open(name_cstr, {}) + if errno != .NONE { + return {}, _get_platform_error(errno) } - defer unix.sys_close(fd) + defer linux.close(fd) return _fstat_internal(fd, allocator) } -_lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + TEMP_ALLOCATOR_GUARD() + name_cstr := temp_cstring(name) or_return - fd := unix.sys_open(name_cstr, _O_RDONLY | _O_PATH | _O_NOFOLLOW) - if fd < 0 { - return {}, _get_platform_error(fd) + fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW}) + if errno != .NONE { + return {}, _get_platform_error(errno) } - defer unix.sys_close(fd) + defer linux.close(fd) return _fstat_internal(fd, allocator) } _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath } - -_stat_internal :: proc(name: string) -> (s: _Stat, res: int) { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) - res = unix.sys_stat(name_cstr, &s) - return -} diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 154a5bbe3..8915d187c 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -7,7 +7,7 @@ import "core:strings" import win32 "core:sys/windows" _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { - if f == nil || f.impl.fd == nil { + if f == nil || (^File_Impl)(f.impl).fd == nil { return {}, nil } @@ -19,42 +19,43 @@ _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { h := _handle(f) switch win32.GetFileType(h) { case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: - fi: File_Info - fi.fullpath = path - fi.name = basename(path) - fi.mode |= file_type_mode(h) + fi := File_Info { + fullpath = path, + name = basename(path), + type = file_type(h), + } return fi, nil } return _file_info_from_get_file_information_by_handle(path, h, allocator) } + _stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator) } + _lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator) } + _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath } - - - full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) { name := name if name == "" { name = "." } - _TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD() - p := win32.utf8_to_utf16(name, _temp_allocator()) + p := win32.utf8_to_utf16(name, temp_allocator()) n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) if n == 0 { return "", _get_platform_error() } - buf := make([]u16, n+1, _temp_allocator()) + buf := make([]u16, n+1, temp_allocator()) n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) if n == 0 { return "", _get_platform_error() @@ -62,7 +63,6 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path return win32.utf16_to_utf8(buf[:n], allocator) } - internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { if len(name) == 0 { return {}, .Not_Exist @@ -99,7 +99,6 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt return _file_info_from_get_file_information_by_handle(name, h, allocator) } - _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { buf := buf N := 0 @@ -120,9 +119,8 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { return buf } - _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) { - if f == nil || f.impl.fd == nil { + if f == nil { return "", nil } h := _handle(f) @@ -131,14 +129,14 @@ _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()) + 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) } _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { - if f == nil || f.impl.fd == nil { + if f == nil { return nil, nil } h := _handle(f) @@ -147,8 +145,8 @@ _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()) + 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 } @@ -159,7 +157,6 @@ _cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string return win32.utf16_to_utf8(buf, allocator) } - basename :: proc(name: string) -> (base: string) { name := name if len(name) > 3 && name[:3] == `\\?` { @@ -185,83 +182,67 @@ basename :: proc(name: string) -> (base: string) { return name } - -file_type_mode :: proc(h: win32.HANDLE) -> File_Mode { +file_type :: proc(h: win32.HANDLE) -> File_Type { switch win32.GetFileType(h) { - case win32.FILE_TYPE_PIPE: - return File_Mode_Named_Pipe - case win32.FILE_TYPE_CHAR: - return File_Mode_Device | File_Mode_Char_Device + case win32.FILE_TYPE_PIPE: return .Named_Pipe + case win32.FILE_TYPE_CHAR: return .Character_Device + case win32.FILE_TYPE_DISK: return .Regular } - return 0 + return .Undetermined } - - -_file_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) { +_file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: int) { if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 { mode |= 0o444 } else { mode |= 0o666 } - is_sym := false if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { is_sym = false } else { is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT } - if is_sym { - mode |= File_Mode_Sym_Link + type = .Symlink } else { if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { - mode |= 0o111 | File_Mode_Dir + type = .Directory + mode |= 0o111 } - if h != nil { - mode |= file_type_mode(h) + type = file_type(h) } } - return } - _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - - fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) - fi.is_directory = fi.mode & File_Mode_Dir != 0 - + type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) + fi.type = type + fi.mode |= mode fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) - fi.fullpath, e = full_path_from_name(name, allocator) fi.name = basename(fi.fullpath) - return } - _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - - fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) - fi.is_directory = fi.mode & File_Mode_Dir != 0 - + type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) + fi.type = type + fi.mode |= mode fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) - fi.fullpath, e = full_path_from_name(name, allocator) fi.name = basename(fi.fullpath) - return } - _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE, allocator: runtime.Allocator) -> (File_Info, Error) { d: win32.BY_HANDLE_FILE_INFORMATION if !win32.GetFileInformationByHandle(h, &d) { @@ -278,25 +259,19 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA // Indicate this is a symlink on FAT file systems ti.ReparseTag = 0 } - fi: File_Info - fi.fullpath = path fi.name = basename(path) fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - - fi.mode |= _file_mode_from_file_attributes(ti.FileAttributes, h, ti.ReparseTag) - fi.is_directory = fi.mode & File_Mode_Dir != 0 - + type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) + fi.type = type + fi.mode |= mode fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) - return fi, nil } - - reserved_names := [?]string{ "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", @@ -357,7 +332,6 @@ _volume_name_len :: proc(path: string) -> int { return 0 } - _is_abs :: proc(path: string) -> bool { if _is_reserved_name(path) { return true diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index f12c2800e..467775e89 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -2,16 +2,96 @@ package os2 import "base:runtime" -create_temp_file :: proc(dir, pattern: string) -> (^File, Error) { - return _create_temp(dir, pattern) +@(private="file") +MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right? + +// Creates a new temperatory file in the directory `dir`. +// +// 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. +// +// The caller must `close` the file once finished with. +@(require_results) +create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) { + TEMP_ALLOCATOR_GUARD() + dir := dir if dir != "" else temp_directory(temp_allocator()) or_return + prefix, suffix := _prefix_and_suffix(pattern) or_return + prefix = temp_join_path(dir, prefix) or_return + + rand_buf: [32]byte + name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator()) + + attempts := 0 + for { + name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix) + f, err = open(name, {.Read, .Write, .Create, .Excl}, 0o666) + if err == .Exist { + close(f) + attempts += 1 + if attempts < MAX_ATTEMPTS { + continue + } + return nil, err + } + return f, err + } } mkdir_temp :: make_directory_temp -make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) { - return _mkdir_temp(dir, pattern, allocator) +// Creates a new temporary directory in the directory `dir`, and returns the path of the new directory. +// +// The directory name 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. +@(require_results) +make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + dir := dir if dir != "" else temp_directory(temp_allocator()) or_return + prefix, suffix := _prefix_and_suffix(pattern) or_return + prefix = temp_join_path(dir, prefix) or_return + + rand_buf: [32]byte + name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator()) + + attempts := 0 + for { + name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix) + err = make_directory(name, 0o700) + if err == nil { + return clone_string(name, allocator) + } + if err == .Exist { + attempts += 1 + if attempts < MAX_ATTEMPTS { + continue + } + return "", err + } + if err == .Not_Exist { + if _, serr := stat(dir, temp_allocator()); serr == .Not_Exist { + return "", serr + } + } + return "", err + } + } temp_dir :: temp_directory +@(require_results) temp_directory :: proc(allocator: runtime.Allocator) -> (string, Error) { return _temp_dir(allocator) } + + + +@(private="file") +temp_join_path :: proc(dir, name: string) -> (string, runtime.Allocator_Error) { + if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) { + return concatenate({dir, name}, temp_allocator(),) + } + + return concatenate({dir, Path_Separator_String, name}, temp_allocator()) +} diff --git a/core/os/os2/temp_file_linux.odin b/core/os/os2/temp_file_linux.odin index dd7ac5c97..92afcde47 100644 --- a/core/os/os2/temp_file_linux.odin +++ b/core/os/os2/temp_file_linux.odin @@ -4,16 +4,6 @@ package os2 import "base:runtime" -_create_temp :: proc(dir, pattern: string) -> (^File, Error) { - //TODO - return nil, nil -} - -_mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) { - //TODO - return "", nil -} - _temp_dir :: proc(allocator: runtime.Allocator) -> (string, Error) { //TODO return "", nil diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index c42da84f5..4c8ab9fb7 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -4,22 +4,14 @@ package os2 import "base:runtime" import win32 "core:sys/windows" -_create_temp :: proc(dir, pattern: string) -> (^File, Error) { - return nil, nil -} - -_mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) { - return "", nil -} - _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { n := win32.GetTempPathW(0, nil) if n == 0 { return "", nil } - _TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD() - b := make([]u16, max(win32.MAX_PATH, n), _temp_allocator()) + b := make([]u16, max(win32.MAX_PATH, n), temp_allocator()) n = win32.GetTempPathW(u32(len(b)), raw_data(b)) if n == 3 && b[1] == ':' && b[2] == '\\' { diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin index 0af461bf5..ca099f7ae 100644 --- a/core/os/os2/user.odin +++ b/core/os/os2/user.odin @@ -1,19 +1,19 @@ package os2 -import "core:strings" import "base:runtime" +@(require_results) user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { #partial switch ODIN_OS { case .Windows: dir = get_env("LocalAppData", allocator) if dir != "" { - dir = strings.clone(dir, allocator) or_return + dir = clone_string(dir, allocator) or_return } case .Darwin: dir = get_env("HOME", allocator) if dir != "" { - dir = strings.concatenate({dir, "/Library/Caches"}, allocator) or_return + dir = concatenate({dir, "/Library/Caches"}, allocator) or_return } case: // All other UNIX systems dir = get_env("XDG_CACHE_HOME", allocator) @@ -22,7 +22,7 @@ user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error if dir == "" { return } - dir = strings.concatenate({dir, "/.cache"}, allocator) or_return + dir = concatenate({dir, "/.cache"}, allocator) or_return } } if dir == "" { @@ -31,17 +31,18 @@ user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error return } +@(require_results) user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { #partial switch ODIN_OS { case .Windows: dir = get_env("AppData", allocator) if dir != "" { - dir = strings.clone(dir, allocator) or_return + dir = clone_string(dir, allocator) or_return } case .Darwin: dir = get_env("HOME", allocator) if dir != "" { - dir = strings.concatenate({dir, "/Library/Application Support"}, allocator) or_return + dir = concatenate({dir, "/Library/Application Support"}, allocator) or_return } case: // All other UNIX systems dir = get_env("XDG_CACHE_HOME", allocator) @@ -50,7 +51,7 @@ user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Erro if dir == "" { return } - dir = strings.concatenate({dir, "/.config"}, allocator) or_return + dir = concatenate({dir, "/.config"}, allocator) or_return } } if dir == "" { @@ -59,6 +60,7 @@ user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Erro return } +@(require_results) user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { env := "HOME" #partial switch ODIN_OS { diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index def0caa13..e3748cce4 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -442,20 +442,20 @@ 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, mode: u16) -> Handle --- + @(link_name="open") _unix_open :: proc(path: cstring, flags: i32, #c_vararg args: ..any) -> 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 --- @(link_name="pread") _unix_pread :: proc(handle: Handle, buffer: rawptr, count: c.size_t, offset: i64) -> int --- @(link_name="pwrite") _unix_pwrite :: proc(handle: Handle, buffer: rawptr, count: c.size_t, offset: i64) -> int --- - @(link_name="lseek") _unix_lseek :: proc(fs: Handle, offset: int, whence: int) -> int --- + @(link_name="lseek") _unix_lseek :: proc(fs: Handle, offset: int, whence: c.int) -> int --- @(link_name="gettid") _unix_gettid :: proc() -> u64 --- @(link_name="getpagesize") _unix_getpagesize :: proc() -> i32 --- @(link_name="stat64") _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- @(link_name="lstat64") _unix_lstat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- @(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: int) -> int --- + @(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="fdopendir$INODE64") _unix_fdopendir_amd64 :: proc(fd: Handle) -> Dir --- @@ -477,7 +477,11 @@ foreign libc { @(link_name="calloc") _unix_calloc :: proc(num, size: int) -> rawptr --- @(link_name="free") _unix_free :: proc(ptr: rawptr) --- @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: int) -> rawptr --- + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="unsetenv") _unix_unsetenv :: proc(cstring) -> c.int --- + @(link_name="setenv") _unix_setenv :: proc(key: cstring, value: cstring, overwrite: c.int) -> c.int --- + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- @(link_name="chdir") _unix_chdir :: proc(buf: cstring) -> c.int --- @(link_name="mkdir") _unix_mkdir :: proc(buf: cstring, mode: u16) -> c.int --- @@ -555,7 +559,7 @@ open :: proc(path: string, flags: int = O_RDWR, mode: int = 0) -> (Handle, Errno err := fchmod(handle, cast(u16)mode) if err != 0 { _unix_close(handle) - return INVALID_HANDLE, cast(Errno)err + return INVALID_HANDLE, err } } @@ -639,7 +643,7 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { assert(fd != -1) - final_offset := i64(_unix_lseek(fd, int(offset), whence)) + final_offset := i64(_unix_lseek(fd, int(offset), c.int(whence))) if final_offset == -1 { return 0, 1 } @@ -883,8 +887,8 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { } defer _unix_free(path_ptr) - path_cstr := transmute(cstring)path_ptr - path = strings.clone( string(path_cstr) ) + path_cstr := cast(cstring)path_ptr + path = strings.clone(string(path_cstr)) return path, ERROR_NONE } @@ -892,11 +896,11 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { access :: proc(path: string, mask: int) -> bool { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) - return _unix_access(cstr, mask) == 0 + return _unix_access(cstr, c.int(mask)) == 0 } flush :: proc(fd: Handle) -> Errno { - return cast(Errno)_unix_fsync(fd) + return cast(Errno)_unix_fsync(fd) } lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { @@ -914,6 +918,27 @@ get_env :: proc(key: string, allocator := context.allocator) -> (value: string) return } +set_env :: proc(key, value: string) -> Errno { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + key_cstring := strings.clone_to_cstring(key, context.temp_allocator) + value_cstring := strings.clone_to_cstring(value, context.temp_allocator) + res := _unix_setenv(key_cstring, value_cstring, 1) + if res < 0 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +unset_env :: proc(key: string) -> Errno { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + s := strings.clone_to_cstring(key, context.temp_allocator) + res := _unix_unsetenv(s) + if res < 0 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + get_current_directory :: proc() -> string { page_size := get_page_size() // NOTE(tetra): See note in os_linux.odin/get_current_directory. buf := make([dynamic]u8, page_size) diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index be86854dd..36ada0948 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -112,15 +112,15 @@ EOWNERDEAD: Errno : 96 O_RDONLY :: 0x00000 O_WRONLY :: 0x00001 O_RDWR :: 0x00002 -O_CREATE :: 0x00040 -O_EXCL :: 0x00080 -O_NOCTTY :: 0x00100 -O_TRUNC :: 0x00200 -O_NONBLOCK :: 0x00800 -O_APPEND :: 0x00400 -O_SYNC :: 0x01000 -O_ASYNC :: 0x02000 -O_CLOEXEC :: 0x80000 +O_NONBLOCK :: 0x00004 +O_APPEND :: 0x00008 +O_ASYNC :: 0x00040 +O_SYNC :: 0x00080 +O_CREATE :: 0x00200 +O_TRUNC :: 0x00400 +O_EXCL :: 0x00800 +O_NOCTTY :: 0x08000 +O_CLOEXEC :: 0100000 SEEK_DATA :: 3 @@ -140,6 +140,8 @@ RTLD_NOLOAD :: 0x02000 MAX_PATH :: 1024 +KINFO_FILE_SIZE :: 1392 + args := _alloc_command_line_arguments() Unix_File_Time :: struct { @@ -159,7 +161,7 @@ blkcnt_t :: i64 blksize_t :: i32 fflags_t :: u32 -when ODIN_ARCH == .amd64 /* LP64 */ { +when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 /* LP64 */ { time_t :: i64 } else { time_t :: i32 @@ -191,6 +193,21 @@ OS_Stat :: struct { lspare: [10]u64, } +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: [MAX_PATH]c.char, +} // since FreeBSD v12 Dirent :: struct { @@ -254,6 +271,8 @@ X_OK :: 1 // Test for execute permission W_OK :: 2 // Test for write permission R_OK :: 4 // Test for read permission +F_KINFO :: 22 + foreign libc { @(link_name="__error") __errno_location :: proc() -> ^c.int --- @@ -274,6 +293,7 @@ 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="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- @@ -365,7 +385,7 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { } file_size :: proc(fd: Handle) -> (i64, Errno) { - s, err := fstat(fd) + s, err := _fstat(fd) if err != ERROR_NONE { return -1, err } @@ -591,9 +611,26 @@ _readlink :: proc(path: string) -> (string, Errno) { return "", Errno{} } -// XXX FreeBSD absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { - return "", Errno(ENOSYS) + // 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: KInfo_File + kinfo.structsize = KINFO_FILE_SIZE + + res := _unix_fcntl(fd, F_KINFO, cast(uintptr)&kinfo) + if res == -1 { + return "", Errno(get_last_error()) + } + + path := strings.clone_from_cstring_bounded(cast(cstring)&kinfo.path[0], len(kinfo.path)) + return path, ERROR_NONE } absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { @@ -611,8 +648,8 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { } defer _unix_free(path_ptr) - path_cstr := transmute(cstring)path_ptr - path = strings.clone( string(path_cstr) ) + + path = strings.clone(string(cstring(path_ptr))) return path, ERROR_NONE } @@ -668,7 +705,9 @@ set_current_directory :: proc(path: string) -> (err: Errno) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_chdir(cstr) - if res == -1 do return Errno(get_last_error()) + if res == -1 { + return Errno(get_last_error()) + } return ERROR_NONE } @@ -706,7 +745,9 @@ get_page_size :: proc() -> int { // NOTE(tetra): The page size never changes, so why do anything complicated // if we don't have to. @static page_size := -1 - if page_size != -1 do return page_size + if page_size != -1 { + return page_size + } page_size = int(_unix_getpagesize()) return page_size diff --git a/core/os/os_js.odin b/core/os/os_js.odin index 910cb8155..8b61cb7ed 100644 --- a/core/os/os_js.odin +++ b/core/os/os_js.odin @@ -1,9 +1,7 @@ //+build js package os -import "base:intrinsics" import "base:runtime" -import "core:unicode/utf16" is_path_separator :: proc(c: byte) -> bool { return c == '/' || c == '\\' @@ -64,13 +62,8 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) unimplemented("core:os procedure not supported on JS target") } - - -// NOTE(bill): Uses startup to initialize it -//stdin := get_std_handle(uint(win32.STD_INPUT_HANDLE)) -//stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE)) -//stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE)) - +stdout: Handle = 1 +stderr: Handle = 2 get_std_handle :: proc "contextless" (h: uint) -> Handle { context = runtime.default_context() diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 545349bc5..ebc1b3600 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -890,8 +890,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { } defer _unix_free(path_ptr) - path_cstr := transmute(cstring)path_ptr - path = strings.clone( string(path_cstr) ) + path = strings.clone(string(cstring(path_ptr))) return path, ERROR_NONE } diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin new file mode 100644 index 000000000..2bda8abf7 --- /dev/null +++ b/core/os/os_netbsd.odin @@ -0,0 +1,787 @@ +package os + +foreign import dl "system:dl" +foreign import libc "system:c" + +import "base:runtime" +import "core:strings" +import "core:c" + +Handle :: distinct i32 +File_Time :: distinct u64 +Errno :: distinct i32 + +INVALID_HANDLE :: ~Handle(0) + +ERROR_NONE: Errno : 0 /* No error */ +EPERM: Errno : 1 /* Operation not permitted */ +ENOENT: Errno : 2 /* No such file or directory */ +EINTR: Errno : 4 /* Interrupted system call */ +ESRCH: Errno : 3 /* No such process */ +EIO: Errno : 5 /* Input/output error */ +ENXIO: Errno : 6 /* Device not configured */ +E2BIG: Errno : 7 /* Argument list too long */ +ENOEXEC: Errno : 8 /* Exec format error */ +EBADF: Errno : 9 /* Bad file descriptor */ +ECHILD: Errno : 10 /* No child processes */ +EDEADLK: Errno : 11 /* Resource deadlock avoided. 11 was EAGAIN */ +ENOMEM: Errno : 12 /* Cannot allocate memory */ +EACCES: Errno : 13 /* Permission denied */ +EFAULT: Errno : 14 /* Bad address */ +ENOTBLK: Errno : 15 /* Block device required */ +EBUSY: Errno : 16 /* Device busy */ +EEXIST: Errno : 17 /* File exists */ +EXDEV: Errno : 18 /* Cross-device link */ +ENODEV: Errno : 19 /* Operation not supported by device */ +ENOTDIR: Errno : 20 /* Not a directory */ +EISDIR: Errno : 21 /* Is a directory */ +EINVAL: Errno : 22 /* Invalid argument */ +ENFILE: Errno : 23 /* Too many open files in system */ +EMFILE: Errno : 24 /* Too many open files */ +ENOTTY: Errno : 25 /* Inappropriate ioctl for device */ +ETXTBSY: Errno : 26 /* Text file busy */ +EFBIG: Errno : 27 /* File too large */ +ENOSPC: Errno : 28 /* No space left on device */ +ESPIPE: Errno : 29 /* Illegal seek */ +EROFS: Errno : 30 /* Read-only file system */ +EMLINK: Errno : 31 /* Too many links */ +EPIPE: Errno : 32 /* Broken pipe */ + +/* math software */ +EDOM: Errno : 33 /* Numerical argument out of domain */ +ERANGE: Errno : 34 /* Result too large or too small */ + +/* non-blocking and interrupt i/o */ +EAGAIN: Errno : 35 /* Resource temporarily unavailable */ +EWOULDBLOCK: Errno : EAGAIN /* Operation would block */ +EINPROGRESS: Errno : 36 /* Operation now in progress */ +EALREADY: Errno : 37 /* Operation already in progress */ + +/* ipc/network software -- argument errors */ +ENOTSOCK: Errno : 38 /* Socket operation on non-socket */ +EDESTADDRREQ: Errno : 39 /* Destination address required */ +EMSGSIZE: Errno : 40 /* Message too long */ +EPROTOTYPE: Errno : 41 /* Protocol wrong type for socket */ +ENOPROTOOPT: Errno : 42 /* Protocol option not available */ +EPROTONOSUPPORT: Errno : 43 /* Protocol not supported */ +ESOCKTNOSUPPORT: Errno : 44 /* Socket type not supported */ +EOPNOTSUPP: Errno : 45 /* Operation not supported */ +EPFNOSUPPORT: Errno : 46 /* Protocol family not supported */ +EAFNOSUPPORT: Errno : 47 /* Address family not supported by protocol family */ +EADDRINUSE: Errno : 48 /* Address already in use */ +EADDRNOTAVAIL: Errno : 49 /* Can't assign requested address */ + +/* ipc/network software -- operational errors */ +ENETDOWN: Errno : 50 /* Network is down */ +ENETUNREACH: Errno : 51 /* Network is unreachable */ +ENETRESET: Errno : 52 /* Network dropped connection on reset */ +ECONNABORTED: Errno : 53 /* Software caused connection abort */ +ECONNRESET: Errno : 54 /* Connection reset by peer */ +ENOBUFS: Errno : 55 /* No buffer space available */ +EISCONN: Errno : 56 /* Socket is already connected */ +ENOTCONN: Errno : 57 /* Socket is not connected */ +ESHUTDOWN: Errno : 58 /* Can't send after socket shutdown */ +ETOOMANYREFS: Errno : 59 /* Too many references: can't splice */ +ETIMEDOUT: Errno : 60 /* Operation timed out */ +ECONNREFUSED: Errno : 61 /* Connection refused */ + +ELOOP: Errno : 62 /* Too many levels of symbolic links */ +ENAMETOOLONG: Errno : 63 /* File name too long */ + +/* should be rearranged */ +EHOSTDOWN: Errno : 64 /* Host is down */ +EHOSTUNREACH: Errno : 65 /* No route to host */ +ENOTEMPTY: Errno : 66 /* Directory not empty */ + +/* quotas & mush */ +EPROCLIM: Errno : 67 /* Too many processes */ +EUSERS: Errno : 68 /* Too many users */ +EDQUOT: Errno : 69 /* Disc quota exceeded */ + +/* Network File System */ +ESTALE: Errno : 70 /* Stale NFS file handle */ +EREMOTE: Errno : 71 /* Too many levels of remote in path */ +EBADRPC: Errno : 72 /* RPC struct is bad */ +ERPCMISMATCH: Errno : 73 /* RPC version wrong */ +EPROGUNAVAIL: Errno : 74 /* RPC prog. not avail */ +EPROGMISMATCH: Errno : 75 /* Program version wrong */ +EPROCUNAVAIL: Errno : 76 /* Bad procedure for program */ + +ENOLCK: Errno : 77 /* No locks available */ +ENOSYS: Errno : 78 /* Function not implemented */ + +EFTYPE: Errno : 79 /* Inappropriate file type or format */ +EAUTH: Errno : 80 /* Authentication error */ +ENEEDAUTH: Errno : 81 /* Need authenticator */ + +/* SystemV IPC */ +EIDRM: Errno : 82 /* Identifier removed */ +ENOMSG: Errno : 83 /* No message of desired type */ +EOVERFLOW: Errno : 84 /* Value too large to be stored in data type */ + +/* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */ +EILSEQ: Errno : 85 /* Illegal byte sequence */ + +/* From IEEE Std 1003.1-2001 */ +/* Base, Realtime, Threads or Thread Priority Scheduling option errors */ +ENOTSUP: Errno : 86 /* Not supported */ + +/* Realtime option errors */ +ECANCELED: Errno : 87 /* Operation canceled */ + +/* Realtime, XSI STREAMS option errors */ +EBADMSG: Errno : 88 /* Bad or Corrupt message */ + +/* XSI STREAMS option errors */ +ENODATA: Errno : 89 /* No message available */ +ENOSR: Errno : 90 /* No STREAM resources */ +ENOSTR: Errno : 91 /* Not a STREAM */ +ETIME: Errno : 92 /* STREAM ioctl timeout */ + +/* File system extended attribute errors */ +ENOATTR: Errno : 93 /* Attribute not found */ + +/* Realtime, XSI STREAMS option errors */ +EMULTIHOP: Errno : 94 /* Multihop attempted */ +ENOLINK: Errno : 95 /* Link has been severed */ +EPROTO: Errno : 96 /* Protocol error */ + +/* Robust mutexes */ +EOWNERDEAD: Errno : 97 /* Previous owner died */ +ENOTRECOVERABLE: Errno : 98 /* State not recoverable */ + +ELAST: Errno : 98 /* Must equal largest errno */ + +/* end of errno */ + +O_RDONLY :: 0x000000000 +O_WRONLY :: 0x000000001 +O_RDWR :: 0x000000002 +O_CREATE :: 0x000000200 +O_EXCL :: 0x000000800 +O_NOCTTY :: 0x000008000 +O_TRUNC :: 0x000000400 +O_NONBLOCK :: 0x000000004 +O_APPEND :: 0x000000008 +O_SYNC :: 0x000000080 +O_ASYNC :: 0x000000040 +O_CLOEXEC :: 0x000400000 + +RTLD_LAZY :: 0x001 +RTLD_NOW :: 0x002 +RTLD_GLOBAL :: 0x100 +RTLD_LOCAL :: 0x200 +RTLD_TRACE :: 0x200 +RTLD_NODELETE :: 0x01000 +RTLD_NOLOAD :: 0x02000 + +F_GETPATH :: 15 + +MAX_PATH :: 1024 +MAXNAMLEN :: 511 + +args := _alloc_command_line_arguments() + +Unix_File_Time :: struct { + seconds: time_t, + nanoseconds: c.long, +} + +dev_t :: u64 +ino_t :: u64 +nlink_t :: u32 +off_t :: i64 +mode_t :: u16 +pid_t :: u32 +uid_t :: u32 +gid_t :: u32 +blkcnt_t :: i64 +blksize_t :: i32 +fflags_t :: u32 +time_t :: i64 + +OS_Stat :: struct { + device_id: dev_t, + mode: mode_t, + _padding0: i16, + ino: ino_t, + nlink: nlink_t, + uid: uid_t, + gid: gid_t, + _padding1: i32, + rdev: dev_t, + + last_access: Unix_File_Time, + modified: Unix_File_Time, + status_change: Unix_File_Time, + birthtime: Unix_File_Time, + + size: off_t, + blocks: blkcnt_t, + block_size: blksize_t, + + flags: fflags_t, + gen: u32, + lspare: [2]u32, +} + +Dirent :: struct { + ino: ino_t, + reclen: u16, + namlen: u16, + type: u8, + name: [MAXNAMLEN + 1]byte, +} + +Dir :: distinct rawptr // DIR* + +// File type +S_IFMT :: 0o170000 // Type of file mask +S_IFIFO :: 0o010000 // Named pipe (fifo) +S_IFCHR :: 0o020000 // Character special +S_IFDIR :: 0o040000 // Directory +S_IFBLK :: 0o060000 // Block special +S_IFREG :: 0o100000 // Regular +S_IFLNK :: 0o120000 // Symbolic link +S_IFSOCK :: 0o140000 // Socket + +// File mode +// Read, write, execute/search by owner +S_IRWXU :: 0o0700 // RWX mask for owner +S_IRUSR :: 0o0400 // R for owner +S_IWUSR :: 0o0200 // W for owner +S_IXUSR :: 0o0100 // X for owner + +// Read, write, execute/search by group +S_IRWXG :: 0o0070 // RWX mask for group +S_IRGRP :: 0o0040 // R for group +S_IWGRP :: 0o0020 // W for group +S_IXGRP :: 0o0010 // X for group + +// Read, write, execute/search by others +S_IRWXO :: 0o0007 // RWX mask for other +S_IROTH :: 0o0004 // R for other +S_IWOTH :: 0o0002 // W for other +S_IXOTH :: 0o0001 // X for other + +S_ISUID :: 0o4000 // Set user id on execution +S_ISGID :: 0o2000 // Set group id on execution +S_ISVTX :: 0o1000 // Directory restrcted delete + +S_ISLNK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK } +S_ISREG :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG } +S_ISDIR :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR } +S_ISCHR :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR } +S_ISBLK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK } +S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO } +S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK } + +F_OK :: 0 // Test for file existance +X_OK :: 1 // Test for execute permission +W_OK :: 2 // Test for write permission +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="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: 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 --- + @(link_name="__lstat50") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="__fstat50") _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="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(buf: 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="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, arg: uintptr) -> c.int --- + + @(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="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(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) -> ! --- +} + +foreign dl { + @(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 --- +} + +@(private) +foreign libc { + _lwp_self :: proc() -> i32 --- +} + +// NOTE(phix): Perhaps share the following functions with FreeBSD if they turn out to be the same in the end. + +is_path_separator :: proc(r: rune) -> bool { + return r == '/' +} + +get_last_error :: proc "contextless" () -> int { + return int(__errno_location()^) +} + +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + handle := _unix_open(cstr, c.int(flags), c.int(mode)) + if handle == -1 { + return INVALID_HANDLE, Errno(get_last_error()) + } + return handle, ERROR_NONE +} + +close :: proc(fd: Handle) -> Errno { + result := _unix_close(fd) + if result == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +// We set a max of 1GB to keep alignment and to be safe. +@(private) +MAX_RW :: 1 << 30 + +read :: proc(fd: Handle, data: []byte) -> (int, Errno) { + to_read := min(c.size_t(len(data)), MAX_RW) + bytes_read := _unix_read(fd, &data[0], to_read) + if bytes_read == -1 { + return -1, Errno(get_last_error()) + } + return int(bytes_read), ERROR_NONE +} + +write :: proc(fd: Handle, data: []byte) -> (int, Errno) { + if len(data) == 0 { + return 0, ERROR_NONE + } + + to_write := min(c.size_t(len(data)), MAX_RW) + bytes_written := _unix_write(fd, &data[0], to_write) + if bytes_written == -1 { + return -1, Errno(get_last_error()) + } + return int(bytes_written), ERROR_NONE +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { + res := _unix_seek(fd, offset, c.int(whence)) + if res == -1 { + return -1, Errno(get_last_error()) + } + return res, ERROR_NONE +} + +file_size :: proc(fd: Handle) -> (i64, Errno) { + s, err := _fstat(fd) + if err != ERROR_NONE { + return -1, err + } + return s.size, ERROR_NONE +} + +rename :: proc(old_path, new_path: string) -> Errno { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) + new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) + res := _unix_rename(old_path_cstr, new_path_cstr) + if res == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +remove :: proc(path: string) -> Errno { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_unlink(path_cstr) + if res == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_mkdir(path_cstr, mode) + if res == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +remove_directory :: proc(path: string) -> Errno { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_rmdir(path_cstr) + if res == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +is_file_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != ERROR_NONE { + return false + } + return S_ISREG(s.mode) +} + +is_file_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Errno + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != ERROR_NONE { + return false + } + return S_ISREG(s.mode) +} + +is_dir_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != ERROR_NONE { + return false + } + return S_ISDIR(s.mode) +} + +is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Errno + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != ERROR_NONE { + return false + } + return S_ISDIR(s.mode) +} + +is_file :: proc {is_file_path, is_file_handle} +is_dir :: proc {is_dir_path, is_dir_handle} + +exists :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cpath := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_access(cpath, O_RDONLY) + return res == 0 +} + +fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) { + result := _unix_fcntl(Handle(fd), c.int(cmd), uintptr(arg)) + if result < 0 { + return 0, Errno(get_last_error()) + } + return int(result), ERROR_NONE +} + +// NOTE(bill): Uses startup to initialize it + +stdin: Handle = 0 +stdout: Handle = 1 +stderr: Handle = 2 + +last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { + s, err := _fstat(fd) + if err != ERROR_NONE { + return 0, err + } + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), ERROR_NONE +} + +last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { + s, err := _stat(name) + if err != ERROR_NONE { + return 0, err + } + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), ERROR_NONE +} + +@private +_stat :: proc(path: string) -> (OS_Stat, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + s: OS_Stat = --- + result := _unix_lstat(cstr, &s) + if result == -1 { + return s, Errno(get_last_error()) + } + return s, ERROR_NONE +} + +@private +_lstat :: proc(path: string) -> (OS_Stat, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_lstat(cstr, &s) + if res == -1 { + return s, Errno(get_last_error()) + } + return s, ERROR_NONE +} + +@private +_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) { + s: OS_Stat = --- + result := _unix_fstat(fd, &s) + if result == -1 { + return s, Errno(get_last_error()) + } + return s, ERROR_NONE +} + +@private +_fdopendir :: proc(fd: Handle) -> (Dir, Errno) { + dirp := _unix_fdopendir(fd) + if dirp == cast(Dir)nil { + return nil, Errno(get_last_error()) + } + return dirp, ERROR_NONE +} + +@private +_closedir :: proc(dirp: Dir) -> Errno { + rc := _unix_closedir(dirp) + if rc != 0 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +@private +_rewinddir :: proc(dirp: Dir) { + _unix_rewinddir(dirp) +} + +@private +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { + result: ^Dirent + rc := _unix_readdir_r(dirp, &entry, &result) + + if rc != 0 { + err = Errno(get_last_error()) + return + } + err = ERROR_NONE + + if result == nil { + end_of_stream = true + return + } + + return +} + +@private +_readlink :: proc(path: string) -> (string, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + + bufsz : uint = MAX_PATH + buf := make([]byte, MAX_PATH) + for { + rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) + if rc == -1 { + delete(buf) + return "", Errno(get_last_error()) + } else if rc == int(bufsz) { + bufsz += MAX_PATH + delete(buf) + buf = make([]byte, bufsz) + } else { + return strings.string_from_ptr(&buf[0], rc), ERROR_NONE + } + } + + return "", Errno{} +} + +absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { + buf: [MAX_PATH]byte + _, err := fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) + if err != ERROR_NONE { + return "", err + } + + path := strings.clone_from_cstring(cstring(&buf[0])) + return path, err +} + +absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { + rel := rel + if rel == "" { + rel = "." + } + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + + rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) + + path_ptr := _unix_realpath(rel_cstr, nil) + if path_ptr == nil { + return "", Errno(get_last_error()) + } + defer _unix_free(path_ptr) + + path = strings.clone(string(cstring(path_ptr))) + + return path, ERROR_NONE +} + +access :: proc(path: string, mask: int) -> (bool, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + cstr := strings.clone_to_cstring(path, context.temp_allocator) + result := _unix_access(cstr, c.int(mask)) + if result == -1 { + return false, Errno(get_last_error()) + } + return true, ERROR_NONE +} + +lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + + path_str := strings.clone_to_cstring(key, context.temp_allocator) + cstr := _unix_getenv(path_str) + if cstr == nil { + return "", false + } + return strings.clone(string(cstr), allocator), true +} + +get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return +} + +get_current_directory :: proc() -> string { + // NOTE(tetra): I would use PATH_MAX here, but I was not able to find + // an authoritative value for it across all systems. + // The largest value I could find was 4096, so might as well use the page size. + page_size := get_page_size() + buf := make([dynamic]u8, page_size) + #no_bounds_check for { + cwd := _unix_getcwd(cstring(&buf[0]), c.size_t(len(buf))) + if cwd != nil { + return string(cwd) + } + if Errno(get_last_error()) != ERANGE { + delete(buf) + return "" + } + resize(&buf, len(buf)+page_size) + } + unreachable() +} + +set_current_directory :: proc(path: string) -> (err: Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_chdir(cstr) + if res == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +exit :: proc "contextless" (code: int) -> ! { + runtime._cleanup_runtime_contextless() + _unix_exit(c.int(code)) +} + +current_thread_id :: proc "contextless" () -> int { + return int(_lwp_self()) +} + +dlopen :: proc(filename: string, flags: int) -> rawptr { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(filename, context.temp_allocator) + handle := _unix_dlopen(cstr, c.int(flags)) + return handle +} + +dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { + assert(handle != nil) + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(symbol, context.temp_allocator) + proc_handle := _unix_dlsym(handle, cstr) + return proc_handle +} + +dlclose :: proc(handle: rawptr) -> bool { + assert(handle != nil) + return _unix_dlclose(handle) == 0 +} + +dlerror :: proc() -> string { + return string(_unix_dlerror()) +} + +get_page_size :: proc() -> int { + // NOTE(tetra): The page size never changes, so why do anything complicated + // if we don't have to. + @static page_size := -1 + if page_size != -1 { + return page_size + } + + page_size = int(_unix_getpagesize()) + return page_size +} + +@(private) +_processor_core_count :: proc() -> int { + count : int = 0 + count_size := size_of(count) + if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 { + if count > 0 { + return count + } + } + + return 1 +} + +_alloc_command_line_arguments :: proc() -> []string { + res := make([]string, len(runtime.args__)) + for arg, i in runtime.args__ { + res[i] = string(arg) + } + return res +} diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index 182d97979..a3b74a524 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -610,8 +610,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { } defer _unix_free(path_ptr) - path_cstr := transmute(cstring)path_ptr - path = strings.clone( string(path_cstr) ) + path = strings.clone(string(cstring(path_ptr))) return path, ERROR_NONE } diff --git a/core/os/os_wasi.odin b/core/os/os_wasi.odin index 9bfd87322..7b7fb4686 100644 --- a/core/os/os_wasi.odin +++ b/core/os/os_wasi.odin @@ -6,6 +6,8 @@ import "base:runtime" Handle :: distinct i32 Errno :: distinct i32 +INVALID_HANDLE :: -1 + ERROR_NONE :: Errno(wasi.errno_t.SUCCESS) O_RDONLY :: 0x00000 @@ -24,7 +26,124 @@ O_CLOEXEC :: 0x80000 stdin: Handle = 0 stdout: Handle = 1 stderr: Handle = 2 -current_dir: Handle = 3 + +args := _alloc_command_line_arguments() + +_alloc_command_line_arguments :: proc() -> (args: []string) { + args = make([]string, len(runtime.args__)) + for &arg, i in args { + arg = string(runtime.args__[i]) + } + return +} + +// WASI works with "preopened" directories, the environment retrieves directories +// (for example with `wasmtime --dir=. module.wasm`) and those given directories +// are the only ones accessible by the application. +// +// So in order to facilitate the `os` API (absolute paths etc.) we keep a list +// of the given directories and match them when needed (notably `os.open`). + +@(private) +Preopen :: struct { + fd: wasi.fd_t, + prefix: string, +} +@(private) +preopens: []Preopen + +@(init, private) +init_preopens :: proc() { + + strip_prefixes :: proc(path: string) -> string { + path := path + loop: for len(path) > 0 { + switch { + case path[0] == '/': + path = path[1:] + case len(path) > 2 && path[0] == '.' && path[1] == '/': + path = path[2:] + case len(path) == 1 && path[0] == '.': + path = path[1:] + case: + break loop + } + } + return path + } + + dyn_preopens: [dynamic]Preopen + loop: for fd := wasi.fd_t(3); ; fd += 1 { + desc, err := wasi.fd_prestat_get(fd) + #partial switch err { + case .BADF: break loop + case: panic("fd_prestat_get returned an unexpected error") + case .SUCCESS: + } + + switch desc.tag { + case .DIR: + buf := make([]byte, desc.dir.pr_name_len) or_else panic("could not allocate memory for filesystem preopens") + if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS { + panic("could not get filesystem preopen dir name") + } + append(&dyn_preopens, Preopen{fd, strip_prefixes(string(buf))}) + } + } + preopens = dyn_preopens[:] +} + +wasi_match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) { + + prefix_matches :: proc(prefix, path: string) -> bool { + // Empty is valid for any relative path. + if len(prefix) == 0 && len(path) > 0 && path[0] != '/' { + return true + } + + if len(path) < len(prefix) { + return false + } + + if path[:len(prefix)] != prefix { + return false + } + + // Only match on full components. + i := len(prefix) + for i > 0 && prefix[i-1] == '/' { + i -= 1 + } + return path[i] == '/' + } + + path := path + for len(path) > 0 && path[0] == '/' { + path = path[1:] + } + + match: Preopen + #reverse for preopen in preopens { + if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) { + match = preopen + } + } + + if match.fd == 0 { + return 0, "", false + } + + relative := path[len(match.prefix):] + for len(relative) > 0 && relative[0] == '/' { + relative = relative[1:] + } + + if len(relative) == 0 { + relative = "." + } + + return match.fd, relative, true +} write :: proc(fd: Handle, data: []byte) -> (int, Errno) { iovs := wasi.ciovec_t(data) @@ -75,7 +194,13 @@ open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errn if mode & O_SYNC == O_SYNC { fdflags += {.SYNC} } - fd, err := wasi.path_open(wasi.fd_t(current_dir),{.SYMLINK_FOLLOW},path,oflags,rights,{},fdflags) + + dir_fd, relative, ok := wasi_match_preopen(path) + if !ok { + return INVALID_HANDLE, Errno(wasi.errno_t.BADF) + } + + fd, err := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags) return Handle(fd), Errno(err) } close :: proc(fd: Handle) -> Errno { diff --git a/core/os/os_windows.odin b/core/os/os_windows.odin index b375e7c66..ecfb0b419 100644 --- a/core/os/os_windows.odin +++ b/core/os/os_windows.odin @@ -178,8 +178,8 @@ WINDOWS_11_BUILD_CUTOFF :: 22_000 get_windows_version_w :: proc() -> win32.OSVERSIONINFOEXW { osvi : win32.OSVERSIONINFOEXW osvi.dwOSVersionInfoSize = size_of(win32.OSVERSIONINFOEXW) - win32.RtlGetVersion(&osvi) - return osvi + win32.RtlGetVersion(&osvi) + return osvi } is_windows_xp :: proc() -> bool { diff --git a/core/os/stat_unix.odin b/core/os/stat_unix.odin index 5e83c0e16..3bd62dfc7 100644 --- a/core/os/stat_unix.odin +++ b/core/os/stat_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku package os import "core:time" diff --git a/core/os/stream.odin b/core/os/stream.odin index 25f31218c..9a168b95c 100644 --- a/core/os/stream.odin +++ b/core/os/stream.odin @@ -32,7 +32,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, } case .Read_At: - when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku) { + when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku) { n_int, os_err = read_at(fd, p, offset) n = i64(n_int) if n == 0 && os_err == 0 { @@ -46,7 +46,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, err = .EOF } case .Write_At: - when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku) { + when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku) { n_int, os_err = write_at(fd, p, offset) n = i64(n_int) if n == 0 && os_err == 0 { @@ -60,7 +60,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, case .Destroy: err = .Empty case .Query: - when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku { + when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku { return io.query_utility({.Close, .Flush, .Read, .Write, .Seek, .Size, .Query}) } else { return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query}) diff --git a/core/path/filepath/path.odin b/core/path/filepath/path.odin index 59a0f7f1c..c3dfa2bb1 100644 --- a/core/path/filepath/path.odin +++ b/core/path/filepath/path.odin @@ -432,7 +432,7 @@ rel :: proc(base_path, target_path: string, allocator := context.allocator) -> ( then `"."` is returned. */ dir :: proc(path: string, allocator := context.allocator) -> string { - context.allocator = allocator + context.allocator = allocator vol := volume_name(path) i := len(path) - 1 for i >= len(vol) && !is_separator(path[i]) { diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index 9beda5557..b44a6a344 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd +//+build linux, darwin, freebsd, openbsd, netbsd package filepath when ODIN_OS == .Darwin { @@ -56,12 +56,12 @@ foreign libc { @(link_name="free") _unix_free :: proc(ptr: rawptr) --- } -when ODIN_OS == .Darwin { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { @(private) foreign libc { @(link_name="__error") __error :: proc() -> ^i32 --- } -} else when ODIN_OS == .OpenBSD { +} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { @(private) foreign libc { @(link_name="__errno") __error :: proc() -> ^i32 --- diff --git a/core/prof/spall/spall_unix.odin b/core/prof/spall/spall_unix.odin index 7915f8c32..e6199d86b 100644 --- a/core/prof/spall/spall_unix.odin +++ b/core/prof/spall/spall_unix.odin @@ -1,5 +1,5 @@ //+private -//+build darwin, freebsd, openbsd +//+build darwin, freebsd, openbsd, netbsd package spall // Only for types. diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index de7379ecc..e6d2bc87a 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -391,7 +391,7 @@ Struct_Field :: struct { struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - if 0 <= i && i < len(s.names) { + if 0 <= i && i < int(s.field_count) { field.name = s.names[i] field.type = s.types[i] field.tag = Struct_Tag(s.tags[i]) @@ -406,7 +406,7 @@ struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) { struct_field_by_name :: proc(T: typeid, name: string) -> (field: Struct_Field) { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - for fname, i in s.names { + for fname, i in s.names[:s.field_count] { if fname == name { field.name = s.names[i] field.type = s.types[i] @@ -427,7 +427,7 @@ struct_field_value_by_name :: proc(a: any, field: string, allow_using := false) ti := runtime.type_info_base(type_info_of(a.id)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - for name, i in s.names { + for name, i in s.names[:s.field_count] { if name == field { return any{ rawptr(uintptr(a.data) + s.offsets[i]), @@ -463,7 +463,7 @@ struct_field_value :: proc(a: any, field: Struct_Field) -> any { struct_field_names :: proc(T: typeid) -> []string { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - return s.names + return s.names[:s.field_count] } return nil } @@ -472,7 +472,7 @@ struct_field_names :: proc(T: typeid) -> []string { struct_field_types :: proc(T: typeid) -> []^Type_Info { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - return s.types + return s.types[:s.field_count] } return nil } @@ -482,7 +482,7 @@ struct_field_types :: proc(T: typeid) -> []^Type_Info { struct_field_tags :: proc(T: typeid) -> []Struct_Tag { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - return transmute([]Struct_Tag)s.tags + return transmute([]Struct_Tag)s.tags[:s.field_count] } return nil } @@ -491,7 +491,7 @@ struct_field_tags :: proc(T: typeid) -> []Struct_Tag { struct_field_offsets :: proc(T: typeid) -> []uintptr { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - return s.offsets + return s.offsets[:s.field_count] } return nil } @@ -501,11 +501,11 @@ struct_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Struct_Field) { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { return soa_zip( - name = s.names, - type = s.types, - tag = transmute([]Struct_Tag)s.tags, - offset = s.offsets, - is_using = s.usings, + name = s.names[:s.field_count], + type = s.types[:s.field_count], + tag = ([^]Struct_Tag)(s.tags)[:s.field_count], + offset = s.offsets[:s.field_count], + is_using = s.usings[:s.field_count], ) } return nil @@ -1409,7 +1409,7 @@ as_pointer :: proc(a: any) -> (value: rawptr, valid: bool) { #partial switch info in ti.variant { case Type_Info_Pointer: valid = true - value = a.data + value = (^rawptr)(a.data)^ case Type_Info_String: valid = true @@ -1569,7 +1569,7 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_ if v.equal != nil { return v.equal(a.data, b.data) } else { - for offset, i in v.offsets { + for offset, i in v.offsets[:v.field_count] { x := rawptr(uintptr(a.data) + offset) y := rawptr(uintptr(b.data) + offset) id := v.types[i].id diff --git a/core/reflect/types.odin b/core/reflect/types.odin index 9cff46a00..4f0674dc8 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -114,17 +114,15 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool { case Type_Info_Struct: y := b.variant.(Type_Info_Struct) or_return - switch { - case len(x.types) != len(y.types), - x.is_packed != y.is_packed, - x.is_raw_union != y.is_raw_union, - x.custom_align != y.custom_align, + switch { + case x.field_count != y.field_count, + x.flags != y.flags, x.soa_kind != y.soa_kind, x.soa_base_type != y.soa_base_type, x.soa_len != y.soa_len: - return false + return false } - for _, i in x.types { + for i in 0.. bool { case Type_Info_Bit_Field: y := b.variant.(Type_Info_Bit_Field) or_return if !are_types_identical(x.backing_type, y.backing_type) { return false } - if len(x.names) != len(y.names) { return false } - for _, i in x.names { + if x.field_count != y.field_count { return false } + for _, i in x.names[:x.field_count] { if x.names[i] != y.names[i] { return false } @@ -368,13 +366,13 @@ is_tuple :: proc(info: ^Type_Info) -> bool { is_struct :: proc(info: ^Type_Info) -> bool { if info == nil { return false } s, ok := type_info_base(info).variant.(Type_Info_Struct) - return ok && !s.is_raw_union + return ok && .raw_union not_in s.flags } @(require_results) is_raw_union :: proc(info: ^Type_Info) -> bool { if info == nil { return false } s, ok := type_info_base(info).variant.(Type_Info_Struct) - return ok && s.is_raw_union + return ok && .raw_union in s.flags } @(require_results) is_union :: proc(info: ^Type_Info) -> bool { @@ -408,7 +406,68 @@ is_relative_multi_pointer :: proc(info: ^Type_Info) -> bool { } +@(require_results) +is_endian_platform :: proc(info: ^Type_Info) -> bool { + if info == nil { return false} + info := info + info = type_info_core(info) + #partial switch v in info.variant { + case Type_Info_Integer: + return v.endianness == .Platform + case Type_Info_Bit_Set: + if v.underlying != nil { + return is_endian_platform(v.underlying) + } + return true + case Type_Info_Pointer: + return true + } + return false +} +@(require_results) +is_endian_little :: proc(info: ^Type_Info) -> bool { + if info == nil { return false} + info := info + info = type_info_core(info) + #partial switch v in info.variant { + case Type_Info_Integer: + if v.endianness == .Platform { + return ODIN_ENDIAN == .Little + } + return v.endianness == .Little + case Type_Info_Bit_Set: + if v.underlying != nil { + return is_endian_platform(v.underlying) + } + return ODIN_ENDIAN == .Little + case Type_Info_Pointer: + return ODIN_ENDIAN == .Little + } + return ODIN_ENDIAN == .Little +} + +@(require_results) +is_endian_big :: proc(info: ^Type_Info) -> bool { + if info == nil { return false} + info := info + info = type_info_core(info) + #partial switch v in info.variant { + case Type_Info_Integer: + if v.endianness == .Platform { + return ODIN_ENDIAN == .Big + } + return v.endianness == .Big + case Type_Info_Bit_Set: + if v.underlying != nil { + return is_endian_platform(v.underlying) + } + return ODIN_ENDIAN == .Big + case Type_Info_Pointer: + return ODIN_ENDIAN == .Big + } + return ODIN_ENDIAN == .Big +} @@ -434,7 +493,7 @@ write_type_builder :: proc(buf: ^strings.Builder, ti: ^Type_Info) -> int { n, _ := write_type_writer(strings.to_writer(buf), ti) return n } -write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -> (n: int, err: io.Error) { +write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -> (n: int, err: io.Error) { defer if n_written != nil { n_written^ += n } @@ -595,15 +654,16 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) - } io.write_string(w, "struct ", &n) or_return - if info.is_packed { io.write_string(w, "#packed ", &n) or_return } - if info.is_raw_union { io.write_string(w, "#raw_union ", &n) or_return } - if info.custom_align { + if .packed in info.flags { io.write_string(w, "#packed ", &n) or_return } + if .raw_union in info.flags { io.write_string(w, "#raw_union ", &n) or_return } + if .no_copy in info.flags { io.write_string(w, "#no_copy ", &n) or_return } + if .align in info.flags { io.write_string(w, "#align(", &n) or_return io.write_i64(w, i64(ti.align), 10, &n) or_return io.write_string(w, ") ", &n) or_return } io.write_byte(w, '{', &n) or_return - for name, i in info.names { + for name, i in info.names[:info.field_count] { if i > 0 { io.write_string(w, ", ", &n) or_return } io.write_string(w, name, &n) or_return io.write_string(w, ": ", &n) or_return @@ -661,7 +721,7 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) - io.write_string(w, "bit_field ", &n) or_return write_type(w, info.backing_type, &n) or_return io.write_string(w, " {", &n) or_return - for name, i in info.names { + for name, i in info.names[:info.field_count] { if i > 0 { io.write_string(w, ", ", &n) or_return } io.write_string(w, name, &n) or_return io.write_string(w, ": ", &n) or_return diff --git a/core/simd/x86/abm.odin b/core/simd/x86/abm.odin index 79b806242..9018a835a 100644 --- a/core/simd/x86/abm.odin +++ b/core/simd/x86/abm.odin @@ -1,7 +1,7 @@ //+build i386, amd64 package simd_x86 -import "core:intrinsics" +import "base:intrinsics" @(require_results, enable_target_feature="lzcnt") _lzcnt_u32 :: #force_inline proc "c" (x: u32) -> u32 { diff --git a/core/simd/x86/aes.odin b/core/simd/x86/aes.odin new file mode 100644 index 000000000..a2cd2e4d3 --- /dev/null +++ b/core/simd/x86/aes.odin @@ -0,0 +1,49 @@ +//+build i386, amd64 +package simd_x86 + +@(require_results, enable_target_feature = "aes") +_mm_aesdec_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return aesdec(a, b) +} + +@(require_results, enable_target_feature = "aes") +_mm_aesdeclast_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return aesdeclast(a, b) +} + +@(require_results, enable_target_feature = "aes") +_mm_aesenc_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return aesenc(a, b) +} + +@(require_results, enable_target_feature = "aes") +_mm_aesenclast_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return aesenclast(a, b) +} + +@(require_results, enable_target_feature = "aes") +_mm_aesimc_si128 :: #force_inline proc "c" (a: __m128i) -> __m128i { + return aesimc(a) +} + +@(require_results, enable_target_feature = "aes") +_mm_aeskeygenassist_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i { + return aeskeygenassist(a, IMM8) +} + + +@(private, default_calling_convention = "none") +foreign _ { + @(link_name = "llvm.x86.aesni.aesdec") + aesdec :: proc(a, b: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aesdeclast") + aesdeclast :: proc(a, b: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aesenc") + aesenc :: proc(a, b: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aesenclast") + aesenclast :: proc(a, b: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aesimc") + aesimc :: proc(a: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aeskeygenassist") + aeskeygenassist :: proc(a: __m128i, #const imm8: u8) -> __m128i --- +} diff --git a/core/simd/x86/cmpxchg16b.odin b/core/simd/x86/cmpxchg16b.odin index d575dd9df..1307a9cf2 100644 --- a/core/simd/x86/cmpxchg16b.odin +++ b/core/simd/x86/cmpxchg16b.odin @@ -1,7 +1,7 @@ //+build amd64 package simd_x86 -import "core:intrinsics" +import "base:intrinsics" cmpxchg16b :: #force_inline proc "c" (dst: ^u128, old, new: u128, $success, $failure: intrinsics.Atomic_Memory_Order) -> (val: u128) { return intrinsics.atomic_compare_exchange_strong_explicit(dst, old, new, success, failure) diff --git a/core/simd/x86/sse.odin b/core/simd/x86/sse.odin index 903a43dfd..4dac50234 100644 --- a/core/simd/x86/sse.odin +++ b/core/simd/x86/sse.odin @@ -1,7 +1,7 @@ //+build i386, amd64 package simd_x86 -import "core:intrinsics" +import "base:intrinsics" import "core:simd" // _MM_SHUFFLE(z, y, x, w) -> (z<<6 | y<<4 | x<<2 | w) diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index a597122f1..426359031 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -1,7 +1,7 @@ //+build i386, amd64 package simd_x86 -import "core:intrinsics" +import "base:intrinsics" import "core:simd" @(enable_target_feature="sse2") @@ -35,7 +35,7 @@ _mm_add_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_add_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.add(transmute(i64x2)a, transmute(i64x2)b) + return simd.add(a, b) } @(require_results, enable_target_feature="sse2") _mm_adds_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { @@ -118,7 +118,7 @@ _mm_sub_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_sub_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.sub(transmute(i64x2)a, transmute(i64x2)b) + return simd.sub(a, b) } @(require_results, enable_target_feature="sse2") _mm_subs_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { @@ -144,19 +144,26 @@ _mm_subs_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { _mm_slli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { shift :: IMM8 & 0xff + // This needs to emit behavior identical to PSLLDQ which is as follows: + // + // TEMP := COUNT + // IF (TEMP > 15) THEN TEMP := 16; FI + // DEST := DEST << (TEMP * 8) + // DEST[MAXVL-1:128] (Unmodified) + return transmute(__m128i)simd.shuffle( - transmute(i8x16)a, i8x16(0), - 0 when shift > 15 else (16 - shift + 0), - 1 when shift > 15 else (16 - shift + 1), - 2 when shift > 15 else (16 - shift + 2), - 3 when shift > 15 else (16 - shift + 3), - 4 when shift > 15 else (16 - shift + 4), - 5 when shift > 15 else (16 - shift + 5), - 6 when shift > 15 else (16 - shift + 6), - 7 when shift > 15 else (16 - shift + 7), - 8 when shift > 15 else (16 - shift + 8), - 9 when shift > 15 else (16 - shift + 9), + transmute(i8x16)a, + 0 when shift > 15 else (16 - shift + 0), + 1 when shift > 15 else (16 - shift + 1), + 2 when shift > 15 else (16 - shift + 2), + 3 when shift > 15 else (16 - shift + 3), + 4 when shift > 15 else (16 - shift + 4), + 5 when shift > 15 else (16 - shift + 5), + 6 when shift > 15 else (16 - shift + 6), + 7 when shift > 15 else (16 - shift + 7), + 8 when shift > 15 else (16 - shift + 8), + 9 when shift > 15 else (16 - shift + 9), 10 when shift > 15 else (16 - shift + 10), 11 when shift > 15 else (16 - shift + 11), 12 when shift > 15 else (16 - shift + 12), @@ -229,7 +236,7 @@ _mm_slli_epi64 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_sll_epi64 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { - return transmute(__m128i)psllq(transmute(i64x2)a, transmute(i64x2)count) + return psllq(a, count) } @(require_results, enable_target_feature="sse2") _mm_srai_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { @@ -275,7 +282,7 @@ _mm_srli_epi64 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_srl_epi64 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { - return transmute(__m128i)psrlq(transmute(i64x2)a, transmute(i64x2)count) + return psrlq(a, count) } @@ -363,7 +370,7 @@ _mm_cvtsi128_si32 :: #force_inline proc "c" (a: __m128i) -> i32 { @(require_results, enable_target_feature="sse2") _mm_set_epi64x :: #force_inline proc "c" (e1, e0: i64) -> __m128i { - return transmute(__m128i)i64x2{e0, e1} + return i64x2{e0, e1} } @(require_results, enable_target_feature="sse2") _mm_set_epi32 :: #force_inline proc "c" (e3, e2, e1, e0: i32) -> __m128i { @@ -435,7 +442,7 @@ _mm_store_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { } @(enable_target_feature="sse2") _mm_storeu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { - storeudq(mem_addr, a) + intrinsics.unaligned_store(mem_addr, a) } @(enable_target_feature="sse2") _mm_storel_epi64 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { @@ -453,7 +460,7 @@ _mm_stream_si32 :: #force_inline proc "c" (mem_addr: ^i32, a: i32) { @(require_results, enable_target_feature="sse2") _mm_move_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { zero := _mm_setzero_si128() - return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)zero, 0, 2) + return simd.shuffle(a, zero, 0, 2) } @@ -545,7 +552,7 @@ _mm_unpackhi_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_unpackhi_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)b, 1, 3) + return simd.shuffle(a, b, 1, 3) } @(require_results, enable_target_feature="sse2") _mm_unpacklo_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { @@ -565,7 +572,7 @@ _mm_unpacklo_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_unpacklo_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)b, 0, 2) + return simd.shuffle(a, b, 0, 2) } @@ -1023,7 +1030,7 @@ when ODIN_ARCH == .amd64 { } @(require_results, enable_target_feature="sse2") _mm_cvtsi128_si64 :: #force_inline proc "c" (a: __m128i) -> i64 { - return simd.extract(transmute(i64x2)a, 0) + return simd.extract(a, 0) } @(require_results, enable_target_feature="sse2") _mm_cvtsi128_si64x :: #force_inline proc "c" (a: __m128i) -> i64 { @@ -1178,8 +1185,6 @@ foreign _ { cvttsd2si :: proc(a: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.cvttps2dq") cvttps2dq :: proc(a: __m128) -> i32x4 --- - @(link_name="llvm.x86.sse2.storeu.dq") - storeudq :: proc(mem_addr: rawptr, a: __m128i) --- @(link_name="llvm.x86.sse2.storeu.pd") storeupd :: proc(mem_addr: rawptr, a: __m128d) --- diff --git a/core/simd/x86/sse3.odin b/core/simd/x86/sse3.odin index cf5f3b2fa..a905a7726 100644 --- a/core/simd/x86/sse3.odin +++ b/core/simd/x86/sse3.odin @@ -1,7 +1,7 @@ //+build i386, amd64 package simd_x86 -import "core:intrinsics" +import "base:intrinsics" import "core:simd" @(require_results, enable_target_feature="sse3") @@ -36,7 +36,7 @@ _mm_lddqu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i { _mm_movedup_pd :: #force_inline proc "c" (a: __m128d) -> __m128d { return simd.shuffle(a, a, 0, 0) } -@(require_results, enable_target_feature="sse3") +@(require_results, enable_target_feature="sse2,sse3") _mm_loaddup_pd :: #force_inline proc "c" (mem_addr: [^]f64) -> __m128d { return _mm_load1_pd(mem_addr) } @@ -65,4 +65,4 @@ foreign _ { hsubps :: proc(a, b: __m128) -> __m128 --- @(link_name = "llvm.x86.sse3.ldu.dq") lddqu :: proc(mem_addr: rawptr) -> i8x16 --- -} \ No newline at end of file +} diff --git a/core/simd/x86/sse41.odin b/core/simd/x86/sse41.odin index 8c306ba4c..c2c1abc2d 100644 --- a/core/simd/x86/sse41.odin +++ b/core/simd/x86/sse41.odin @@ -106,7 +106,7 @@ _mm_packus_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse4.1") _mm_cmpeq_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.lanes_eq(transmute(i64x2)a, transmute(i64x2)b) + return transmute(__m128i)simd.lanes_eq(a, b) } @(require_results, enable_target_feature="sse4.1") _mm_cvtepi8_epi16 :: #force_inline proc "c" (a: __m128i) -> __m128i { @@ -124,7 +124,7 @@ _mm_cvtepi8_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { _mm_cvtepi8_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(i8x16)a y := simd.shuffle(x, x, 0, 1) - return transmute(__m128i)i64x2(y) + return i64x2(y) } @(require_results, enable_target_feature="sse4.1") _mm_cvtepi16_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { @@ -136,13 +136,13 @@ _mm_cvtepi16_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { _mm_cvtepi16_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(i16x8)a y := simd.shuffle(x, x, 0, 1) - return transmute(__m128i)i64x2(y) + return i64x2(y) } @(require_results, enable_target_feature="sse4.1") _mm_cvtepi32_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(i32x4)a y := simd.shuffle(x, x, 0, 1) - return transmute(__m128i)i64x2(y) + return i64x2(y) } @(require_results, enable_target_feature="sse4.1") _mm_cvtepu8_epi16 :: #force_inline proc "c" (a: __m128i) -> __m128i { @@ -160,7 +160,7 @@ _mm_cvtepu8_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { _mm_cvtepu8_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(u8x16)a y := simd.shuffle(x, x, 0, 1) - return transmute(__m128i)i64x2(y) + return i64x2(y) } @(require_results, enable_target_feature="sse4.1") _mm_cvtepu16_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { @@ -172,13 +172,13 @@ _mm_cvtepu16_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { _mm_cvtepu16_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(u16x8)a y := simd.shuffle(x, x, 0, 1) - return transmute(__m128i)i64x2(y) + return i64x2(y) } @(require_results, enable_target_feature="sse4.1") _mm_cvtepu32_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(u32x4)a y := simd.shuffle(x, x, 0, 1) - return transmute(__m128i)i64x2(y) + return i64x2(y) } @(require_results, enable_target_feature="sse4.1") _mm_dp_pd :: #force_inline proc "c" (a, b: __m128d, $IMM8: u8) -> __m128d { @@ -242,7 +242,7 @@ _mm_minpos_epu16 :: #force_inline proc "c" (a: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse4.1") _mm_mul_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)pmuldq(transmute(i32x4)a, transmute(i32x4)b) + return pmuldq(transmute(i32x4)a, transmute(i32x4)b) } @(require_results, enable_target_feature="sse4.1") _mm_mullo_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { @@ -254,21 +254,21 @@ _mm_mpsadbw_epu8 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u8) -> __m128i } @(require_results, enable_target_feature="sse4.1") _mm_testz_si128 :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { - return ptestz(transmute(i64x2)a, transmute(i64x2)mask) + return ptestz(a, mask) } @(require_results, enable_target_feature="sse4.1") _mm_testc_si128 :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { - return ptestc(transmute(i64x2)a, transmute(i64x2)mask) + return ptestc(a, mask) } @(require_results, enable_target_feature="sse4.1") _mm_testnzc_si128 :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { - return ptestnzc(transmute(i64x2)a, transmute(i64x2)mask) + return ptestnzc(a, mask) } @(require_results, enable_target_feature="sse4.1") _mm_test_all_zeros :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { return _mm_testz_si128(a, mask) } -@(require_results, enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse2,sse4.1") _mm_test_all_ones :: #force_inline proc "c" (a: __m128i) -> i32 { return _mm_testc_si128(a, _mm_cmpeq_epi32(a, a)) } @@ -349,4 +349,4 @@ foreign _ { ptestc :: proc(a, mask: i64x2) -> i32 --- @(link_name = "llvm.x86.sse41.ptestnzc") ptestnzc :: proc(a, mask: i64x2) -> i32 --- -} \ No newline at end of file +} diff --git a/core/simd/x86/sse42.odin b/core/simd/x86/sse42.odin index 621346342..7a674176b 100644 --- a/core/simd/x86/sse42.odin +++ b/core/simd/x86/sse42.odin @@ -94,7 +94,7 @@ _mm_crc32_u32 :: #force_inline proc "c" (crc: u32, v: u32) -> u32 { } @(require_results, enable_target_feature="sse4.2") _mm_cmpgt_epi64 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { - return transmute(__m128i)simd.lanes_gt(transmute(i64x2)a, transmute(i64x2)b) + return transmute(__m128i)simd.lanes_gt(a, b) } when ODIN_ARCH == .amd64 { diff --git a/core/simd/x86/ssse3.odin b/core/simd/x86/ssse3.odin index 0264a1c93..2026c7f53 100644 --- a/core/simd/x86/ssse3.odin +++ b/core/simd/x86/ssse3.odin @@ -1,7 +1,7 @@ //+build i386, amd64 package simd_x86 -import "core:intrinsics" +import "base:intrinsics" import "core:simd" _ :: simd diff --git a/core/slice/permute.odin b/core/slice/permute.odin new file mode 100644 index 000000000..42b6d4129 --- /dev/null +++ b/core/slice/permute.odin @@ -0,0 +1,105 @@ +package slice + +import "base:runtime" + +// An in-place permutation iterator. +Permutation_Iterator :: struct($T: typeid) { + index: int, + slice: []T, + counters: []int, +} + +/* +Make an iterator to permute a slice in-place. + +*Allocates Using Provided Allocator* + +This procedure allocates some state to assist in permutation and does not make +a copy of the underlying slice. If you want to permute a slice without altering +the underlying data, use `clone` to create a copy, then permute that instead. + +Inputs: +- slice: The slice to permute. +- allocator: (default is context.allocator) + +Returns: +- iter: The iterator, to be passed to `permute`. +- error: An `Allocator_Error`, if allocation failed. +*/ +make_permutation_iterator :: proc( + slice: []$T, + allocator := context.allocator, +) -> ( + iter: Permutation_Iterator(T), + error: runtime.Allocator_Error, +) #optional_allocator_error { + iter.slice = slice + iter.counters = make([]int, len(iter.slice), allocator) or_return + + return +} +/* +Free the state allocated by `make_permutation_iterator`. + +Inputs: +- iter: The iterator created by `make_permutation_iterator`. +- allocator: The allocator used to create the iterator. (default is context.allocator) +*/ +destroy_permutation_iterator :: proc( + iter: Permutation_Iterator($T), + allocator := context.allocator, +) { + delete(iter.counters, allocator = allocator) +} +/* +Permute a slice in-place. + +Note that the first iteration will always be the original, unpermuted slice. + +Inputs: +- iter: The iterator created by `make_permutation_iterator`. + +Returns: +- ok: True if the permutation succeeded, false if the iteration is complete. +*/ +permute :: proc(iter: ^Permutation_Iterator($T)) -> (ok: bool) { + // This is an iterative, resumable implementation of Heap's algorithm. + // + // The original algorithm was described by B. R. Heap as "Permutations by + // interchanges" in The Computer Journal, 1963. + // + // This implementation is based on the nonrecursive version described by + // Robert Sedgewick in "Permutation Generation Methods" which was published + // in ACM Computing Surveys in 1977. + + i := iter.index + + if i == 0 { + iter.index = 1 + return true + } + + n := len(iter.counters) + #no_bounds_check for i < n { + if iter.counters[i] < i { + if i & 1 == 0 { + iter.slice[0], iter.slice[i] = iter.slice[i], iter.slice[0] + } else { + iter.slice[iter.counters[i]], iter.slice[i] = iter.slice[i], iter.slice[iter.counters[i]] + } + + iter.counters[i] += 1 + i = 1 + + break + } else { + iter.counters[i] = 0 + i += 1 + } + } + if i == n { + return false + } + iter.index = i + return true +} diff --git a/core/slice/slice.odin b/core/slice/slice.odin index dd8d9868a..043e51aa5 100644 --- a/core/slice/slice.odin +++ b/core/slice/slice.odin @@ -156,8 +156,7 @@ linear_search_proc :: proc(array: $A/[]$T, f: proc(T) -> bool) -> (index: int, f */ @(require_results) binary_search :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool) - where intrinsics.type_is_ordered(T) #no_bounds_check -{ + where intrinsics.type_is_ordered(T) #no_bounds_check { return binary_search_by(array, key, cmp_proc(T)) } @@ -180,7 +179,7 @@ binary_search_by :: proc(array: $A/[]$T, key: T, f: proc(T, T) -> Ordering) -> ( } @(require_results) -equal :: proc(a, b: $T/[]$E) -> bool where intrinsics.type_is_comparable(E) { +equal :: proc(a, b: $T/[]$E) -> bool where intrinsics.type_is_comparable(E) #no_bounds_check { if len(a) != len(b) { return false } @@ -222,7 +221,7 @@ prefix_length :: proc(a, b: $T/[]$E) -> (n: int) where intrinsics.type_is_compar } @(require_results) -has_prefix :: proc(array: $T/[]$E, needle: E) -> bool where intrinsics.type_is_comparable(E) { +has_prefix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) { n := len(needle) if len(array) >= n { return equal(array[:n], needle) @@ -232,7 +231,7 @@ has_prefix :: proc(array: $T/[]$E, needle: E) -> bool where intrinsics.type_is_c @(require_results) -has_suffix :: proc(array: $T/[]$E, needle: E) -> bool where intrinsics.type_is_comparable(E) { +has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) { array := array m, n := len(array), len(needle) if m >= n { @@ -495,8 +494,10 @@ unique :: proc(s: $S/[]$T) -> S where intrinsics.type_is_comparable(T) #no_bound } i := 1 for j in 1.. bool) -> S #no_bounds_check { } i := 1 for j in 1.. []intrinsics.type_elem_type(T) where intrinsics.type_is_enumerated_array(T) { return ([^]intrinsics.type_elem_type(T))(ptr)[:len(T)] } + +// Turn a `[]E` into `bit_set[E]` +// e.g.: +// bs := slice.enum_slice_to_bitset(my_flag_slice, rl.ConfigFlags) +@(require_results) +enum_slice_to_bitset :: proc(enums: []$E, $T: typeid/bit_set[E]) -> (bits: T) where intrinsics.type_is_enum(E), intrinsics.type_bit_set_elem_type(T) == E { + for v in enums { + bits += {v} + } + return +} + +// Turn a `bit_set[E]` into a `[]E` +// e.g.: +// sl := slice.bitset_to_enum_slice(flag_buf[:], bs) +@(require_results) +bitset_to_enum_slice_with_buffer :: proc(buf: []$E, bs: $T) -> (slice: []E) where intrinsics.type_is_enum(E), intrinsics.type_bit_set_elem_type(T) == E { + count := 0 + for v in bs { + buf[count] = v + count += 1 + } + return buf[:count] +} + +// Turn a `bit_set[E]` into a `[]E`, allocates +// e.g.: +// sl := slice.bitset_to_enum_slice(bs) +@(require_results) +bitset_to_enum_slice_with_make :: proc(bs: $T, $E: typeid, allocator := context.allocator) -> (slice: []E) where intrinsics.type_is_enum(E), intrinsics.type_bit_set_elem_type(T) == E { + ones := intrinsics.count_ones(transmute(E)bs) + buf := make([]E, int(ones), allocator) + return bitset_to_enum_slice(buf, bs) +} + +bitset_to_enum_slice :: proc{bitset_to_enum_slice_with_make, bitset_to_enum_slice_with_buffer} diff --git a/core/strconv/generic_float.odin b/core/strconv/generic_float.odin index 4ad42a647..b049f0fe1 100644 --- a/core/strconv/generic_float.odin +++ b/core/strconv/generic_float.odin @@ -104,8 +104,7 @@ generic_ftoa :: proc(buf: []byte, val: f64, fmt: byte, precision, bit_size: int) } else { switch fmt { case 'e', 'E': - prec += 1 - decimal.round(d, prec) + decimal.round(d, prec + 1) case 'f', 'F': decimal.round(d, d.decimal_point+prec) case 'g', 'G': @@ -376,7 +375,7 @@ decimal_to_float_bits :: proc(d: ^decimal.Decimal, info: ^Float_Info) -> (b: u64 return } - @static power_table := [?]int{1, 3, 6, 9, 13, 16, 19, 23, 26} + @(static, rodata) power_table := [?]int{1, 3, 6, 9, 13, 16, 19, 23, 26} exp = 0 for d.decimal_point > 0 { diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index c2e81ef3e..dce9f834a 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -727,10 +727,10 @@ Example: import "core:strconv" parse_f32_example :: proc() { n, ok := strconv.parse_f32("1234eee") - fmt.println(n, ok) + fmt.printfln("%.3f %v", n, ok) n, ok = strconv.parse_f32("5678e2") - fmt.println(n, ok) + fmt.printfln("%.3f %v", n, ok) } Output: @@ -760,10 +760,10 @@ Example: import "core:strconv" parse_f64_example :: proc() { n, ok := strconv.parse_f64("1234eee") - fmt.println(n, ok) + fmt.printfln("%.3f %v", n, ok) n, ok = strconv.parse_f64("5678e2") - fmt.println(n, ok) + fmt.printfln("%.3f %v", n, ok) } Output: @@ -796,10 +796,10 @@ Example: import "core:strconv" parse_f32_prefix_example :: proc() { n, _, ok := strconv.parse_f32_prefix("1234eee") - fmt.println(n, ok) + fmt.printfln("%.3f %v", n, ok) n, _, ok = strconv.parse_f32_prefix("5678e2") - fmt.println(n, ok) + fmt.printfln("%.3f %v", n, ok) } Output: @@ -831,21 +831,25 @@ Example: import "core:strconv" parse_f64_prefix_example :: proc() { n, _, ok := strconv.parse_f64_prefix("12.34eee") - fmt.println(n, ok) + fmt.printfln("%.3f %v", n, ok) n, _, ok = strconv.parse_f64_prefix("12.34e2") - fmt.println(n, ok) + fmt.printfln("%.3f %v", n, ok) + + n, _, ok = strconv.parse_f64_prefix("13.37 hellope") + fmt.printfln("%.3f %v", n, ok) } Output: 0.000 false 1234.000 true + 13.370 true **Returns** - value: The parsed 64-bit floating point number. - nr: The length of the parsed substring. -- ok: `false` if a base 10 float could not be found, or if the input string contained more than just the number. +- ok: `false` if a base 10 float could not be found */ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { common_prefix_len_ignore_case :: proc "contextless" (s, prefix: string) -> int { @@ -878,13 +882,16 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { s = s[1:] fallthrough case 'i', 'I': - n = common_prefix_len_ignore_case(s, "infinity") - if 3 < n && n < 8 { // "inf" or "infinity" - n = 3 - } - if n == 3 || n == 8 { + m := common_prefix_len_ignore_case(s, "infinity") + if 3 <= m && m < 9 { // "inf" to "infinity" f = 0h7ff00000_00000000 if sign == 1 else 0hfff00000_00000000 - n = nsign + 3 + if m == 8 { + // We only count the entire prefix if it is precisely "infinity". + n = nsign + m + } else { + // The string was either only "inf" or incomplete. + n = nsign + 3 + } ok = true return } @@ -925,6 +932,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { nd := 0 nd_mant := 0 decimal_point := 0 + trailing_zeroes_nd := -1 loop: for ; i < len(s); i += 1 { switch c := s[i]; true { case c == '_': @@ -940,9 +948,16 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { case '0' <= c && c <= '9': saw_digits = true - if c == '0' && nd == 0 { - decimal_point -= 1 - continue loop + if c == '0' { + if nd == 0 { + decimal_point -= 1 + continue loop + } + if trailing_zeroes_nd == -1 { + trailing_zeroes_nd = nd + } + } else { + trailing_zeroes_nd = -1 } nd += 1 if nd_mant < MAX_MANT_DIGITS { @@ -974,6 +989,14 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { if !saw_dot { decimal_point = nd } + if trailing_zeroes_nd > 0 { + trailing_zeroes_nd = nd_mant - trailing_zeroes_nd + } + for /**/; trailing_zeroes_nd > 0; trailing_zeroes_nd -= 1 { + mantissa /= base + nd_mant -= 1 + nd -= 1 + } if base == 16 { decimal_point *= 4 nd_mant *= 4 @@ -1088,7 +1111,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { } trunc_block: if !trunc { - @static pow10 := [?]f64{ + @(static, rodata) pow10 := [?]f64{ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, @@ -1124,6 +1147,275 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { ok = !overflow return } +/* +Parses a 128-bit complex number from a string + +**Inputs** +- str: The input string containing a 128-bit complex number. +- 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_complex128_example :: proc() { + n: int + c, ok := strconv.parse_complex128("3+1i", &n) + fmt.printfln("%v %i %t", c, n, ok) + + c, ok = strconv.parse_complex128("5+7i hellope", &n) + fmt.printfln("%v %i %t", c, n, ok) + } + +Output: + + 3+1i 4 true + 5+7i 4 false + +**Returns** +- value: The parsed 128-bit complex number. +- ok: `false` if a complex number could not be found, or if the input string contained more than just the number. +*/ +parse_complex128 :: proc(str: string, n: ^int = nil) -> (value: complex128, ok: bool) { + real_value, imag_value: f64 + nr_r, nr_i: int + + real_value, nr_r, _ = parse_f64_prefix(str) + imag_value, nr_i, _ = parse_f64_prefix(str[nr_r:]) + + i_parsed := len(str) >= nr_r + nr_i + 1 && str[nr_r + nr_i] == 'i' + if !i_parsed { + // No `i` means we refuse to treat the second float we parsed as an + // imaginary value. + imag_value = 0 + nr_i = 0 + } + + ok = i_parsed && len(str) == nr_r + nr_i + 1 + + if n != nil { + n^ = nr_r + nr_i + (1 if i_parsed else 0) + } + + value = complex(real_value, imag_value) + return +} +/* +Parses a 64-bit complex number from a string + +**Inputs** +- str: The input string containing a 64-bit complex number. +- 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_complex64_example :: proc() { + n: int + c, ok := strconv.parse_complex64("3+1i", &n) + fmt.printfln("%v %i %t", c, n, ok) + + c, ok = strconv.parse_complex64("5+7i hellope", &n) + fmt.printfln("%v %i %t", c, n, ok) + } + +Output: + + 3+1i 4 true + 5+7i 4 false + +**Returns** +- value: The parsed 64-bit complex number. +- ok: `false` if a complex number could not be found, or if the input string contained more than just the number. +*/ +parse_complex64 :: proc(str: string, n: ^int = nil) -> (value: complex64, ok: bool) { + v: complex128 = --- + v, ok = parse_complex128(str, n) + return cast(complex64)v, ok +} +/* +Parses a 32-bit complex number from a string + +**Inputs** +- str: The input string containing a 32-bit complex number. +- 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_complex32_example :: proc() { + n: int + c, ok := strconv.parse_complex32("3+1i", &n) + fmt.printfln("%v %i %t", c, n, ok) + + c, ok = strconv.parse_complex32("5+7i hellope", &n) + fmt.printfln("%v %i %t", c, n, ok) + } + +Output: + + 3+1i 4 true + 5+7i 4 false + +**Returns** +- value: The parsed 32-bit complex number. +- ok: `false` if a complex number could not be found, or if the input string contained more than just the number. +*/ +parse_complex32 :: proc(str: string, n: ^int = nil) -> (value: complex32, ok: bool) { + v: complex128 = --- + v, ok = parse_complex128(str, n) + return cast(complex32)v, ok +} +/* +Parses a 256-bit quaternion from a string + +**Inputs** +- str: The input string containing a 256-bit quaternion. +- 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_quaternion256_example :: proc() { + n: int + q, ok := strconv.parse_quaternion256("1+2i+3j+4k", &n) + fmt.printfln("%v %i %t", q, n, ok) + + q, ok = strconv.parse_quaternion256("1+2i+3j+4k hellope", &n) + fmt.printfln("%v %i %t", q, n, ok) + } + +Output: + + 1+2i+3j+4k 10 true + 1+2i+3j+4k 10 false + +**Returns** +- value: The parsed 256-bit quaternion. +- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. +*/ +parse_quaternion256 :: proc(str: string, n: ^int = nil) -> (value: quaternion256, ok: bool) { + iterate_and_assign :: proc (iter: ^string, terminator: byte, nr_total: ^int, state: bool) -> (value: f64, ok: bool) { + if !state { + return + } + + nr: int + value, nr, _ = parse_f64_prefix(iter^) + iter^ = iter[nr:] + + if len(iter) > 0 && iter[0] == terminator { + iter^ = iter[1:] + nr_total^ += nr + 1 + ok = true + } else { + value = 0 + } + + return + } + + real_value, imag_value, jmag_value, kmag_value: f64 + nr: int + + real_value, nr, _ = parse_f64_prefix(str) + iter := str[nr:] + + // Need to have parsed at least something in order to get started. + ok = nr > 0 + + // Quaternion parsing is done this way to honour the rest of the API with + // regards to partial parsing. Otherwise, we could error out early. + imag_value, ok = iterate_and_assign(&iter, 'i', &nr, ok) + jmag_value, ok = iterate_and_assign(&iter, 'j', &nr, ok) + kmag_value, ok = iterate_and_assign(&iter, 'k', &nr, ok) + + if len(iter) != 0 { + ok = false + } + + if n != nil { + n^ = nr + } + + value = quaternion( + real = real_value, + imag = imag_value, + jmag = jmag_value, + kmag = kmag_value) + return +} +/* +Parses a 128-bit quaternion from a string + +**Inputs** +- str: The input string containing a 128-bit quaternion. +- 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_quaternion128_example :: proc() { + n: int + q, ok := strconv.parse_quaternion128("1+2i+3j+4k", &n) + fmt.printfln("%v %i %t", q, n, ok) + + q, ok = strconv.parse_quaternion128("1+2i+3j+4k hellope", &n) + fmt.printfln("%v %i %t", q, n, ok) + } + +Output: + + 1+2i+3j+4k 10 true + 1+2i+3j+4k 10 false + +**Returns** +- value: The parsed 128-bit quaternion. +- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. +*/ +parse_quaternion128 :: proc(str: string, n: ^int = nil) -> (value: quaternion128, ok: bool) { + v: quaternion256 = --- + v, ok = parse_quaternion256(str, n) + return cast(quaternion128)v, ok +} +/* +Parses a 64-bit quaternion from a string + +**Inputs** +- str: The input string containing a 64-bit quaternion. +- 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_quaternion64_example :: proc() { + n: int + q, ok := strconv.parse_quaternion64("1+2i+3j+4k", &n) + fmt.printfln("%v %i %t", q, n, ok) + + q, ok = strconv.parse_quaternion64("1+2i+3j+4k hellope", &n) + fmt.printfln("%v %i %t", q, n, ok) + } + +Output: + + 1+2i+3j+4k 10 true + 1+2i+3j+4k 10 false + +**Returns** +- value: The parsed 64-bit quaternion. +- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. +*/ +parse_quaternion64 :: proc(str: string, n: ^int = nil) -> (value: quaternion64, ok: bool) { + v: quaternion256 = --- + v, ok = parse_quaternion256(str, n) + return cast(quaternion64)v, ok +} /* Appends a boolean value as a string to the given buffer @@ -1283,7 +1575,7 @@ Example: import "core:fmt" import "core:strconv" atof_example :: proc() { - fmt.println(strconv.atof("3.14")) + fmt.printfln("%.3f", strconv.atof("3.14")) } Output: diff --git a/core/strings/builder.odin b/core/strings/builder.odin index 72eb815f9..6bb0b2018 100644 --- a/core/strings/builder.odin +++ b/core/strings/builder.odin @@ -24,7 +24,7 @@ Builder :: struct { buf: [dynamic]byte, } /* -Produces a Builder with a default length of 0 and cap of 16 +Produces an empty Builder *Allocates Using Provided Allocator* @@ -39,7 +39,7 @@ builder_make_none :: proc(allocator := context.allocator, loc := #caller_locatio return Builder{buf=make([dynamic]byte, allocator, loc) or_return }, nil } /* -Produces a Builder with a specified length and cap of max(16,len) byte buffer +Produces a Builder with specified length and capacity `len`. *Allocates Using Provided Allocator* @@ -55,7 +55,7 @@ builder_make_len :: proc(len: int, allocator := context.allocator, loc := #calle return Builder{buf=make([dynamic]byte, len, allocator, loc) or_return }, nil } /* -Produces a Builder with a specified length and cap +Produces a Builder with specified length `len` and capacity `cap`. *Allocates Using Provided Allocator* @@ -103,7 +103,7 @@ builder_make :: proc{ builder_make_len_cap, } /* -Initializes a Builder with a length of 0 and cap of 16 +Initializes an empty Builder It replaces the existing `buf` *Allocates Using Provided Allocator* @@ -121,7 +121,7 @@ builder_init_none :: proc(b: ^Builder, allocator := context.allocator, loc := #c return b, nil } /* -Initializes a Builder with a specified length and cap, which is max(len,16) +Initializes a Builder with specified length and capacity `len`. It replaces the existing `buf` *Allocates Using Provided Allocator* @@ -140,7 +140,7 @@ builder_init_len :: proc(b: ^Builder, len: int, allocator := context.allocator, return b, nil } /* -Initializes a Builder with a specified length and cap +Initializes a Builder with specified length `len` and capacity `cap`. It replaces the existing `buf` Inputs: @@ -286,6 +286,20 @@ to_string :: proc(b: Builder) -> (res: string) { return string(b.buf[:]) } /* +Appends a trailing null byte after the end of the current Builder byte buffer and then casts it to a cstring + +Inputs: +- b: A pointer to builder + +Returns: +- res: A cstring of the Builder's buffer +*/ +to_cstring :: proc(b: ^Builder) -> (res: cstring) { + append(&b.buf, 0) + pop(&b.buf) + return cstring(raw_data(b.buf)) +} +/* Returns the length of the Builder's buffer, in bytes Inputs: @@ -350,9 +364,9 @@ Output: ab */ -write_byte :: proc(b: ^Builder, x: byte) -> (n: int) { +write_byte :: proc(b: ^Builder, x: byte, loc := #caller_location) -> (n: int) { n0 := len(b.buf) - append(&b.buf, x) + append(&b.buf, x, loc) n1 := len(b.buf) return n1-n0 } @@ -380,9 +394,9 @@ NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` Returns: - n: The number of bytes appended */ -write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) { +write_bytes :: proc(b: ^Builder, x: []byte, loc := #caller_location) -> (n: int) { n0 := len(b.buf) - append(&b.buf, ..x) + append(&b.buf, ..x, loc=loc) n1 := len(b.buf) return n1-n0 } @@ -709,11 +723,11 @@ write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n: return write_string(b, s) } /* -Writes a f32 value to the Builder and returns the number of characters written +Writes a f64 value to the Builder and returns the number of characters written Inputs: - b: A pointer to the Builder -- f: The f32 value to be appended +- f: The f64 value to be appended - fmt: The format byte - always_signed: Optional boolean flag to always include the sign diff --git a/core/strings/intern.odin b/core/strings/intern.odin index 88eea3c50..4c270980c 100644 --- a/core/strings/intern.odin +++ b/core/strings/intern.odin @@ -37,7 +37,7 @@ Returns: intern_init :: proc(m: ^Intern, allocator := context.allocator, map_allocator := context.allocator, loc := #caller_location) -> (err: mem.Allocator_Error) { m.allocator = allocator m.entries = make(map[string]^Intern_Entry, 16, map_allocator, loc) or_return - return nil + return nil } /* Frees the map and all its content allocated using the `.allocator`. diff --git a/core/strings/strings.odin b/core/strings/strings.odin index 13c53f48e..e9b50bab0 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -91,8 +91,8 @@ Inputs: Returns: - res: A string created from the null-terminated byte pointer and length */ -string_from_null_terminated_ptr :: proc(ptr: ^byte, len: int) -> (res: string) { - s := transmute(string)mem.Raw_String{ptr, len} +string_from_null_terminated_ptr :: proc(ptr: [^]byte, len: int) -> (res: string) { + s := string(ptr[:len]) s = truncate_to_byte(s, 0) return s } @@ -531,6 +531,9 @@ Output: has_prefix :: proc(s, prefix: string) -> (result: bool) { return len(s) >= len(prefix) && s[0:len(prefix)] == prefix } + +starts_with :: has_prefix + /* Determines if a string `s` ends with a given `suffix` @@ -562,6 +565,9 @@ Output: has_suffix :: proc(s, suffix: string) -> (result: bool) { return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix } + +ends_with :: has_suffix + /* Joins a slice of strings `a` with a `sep` string @@ -1001,11 +1007,6 @@ Returns: */ @private _split_iterator :: proc(s: ^string, sep: string, sep_save: int) -> (res: string, ok: bool) { - // stop once the string is empty or nil - if s == nil || len(s^) == 0 { - return - } - if sep == "" { res = s[:] ok = true @@ -2414,9 +2415,6 @@ trim_right_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, sta } // Procedure for `trim_*_proc` variants, which has a string rawptr cast + rune comparison is_in_cutset :: proc(state: rawptr, r: rune) -> (res: bool) { - if state == nil { - return false - } cutset := (^string)(state)^ for c in cutset { if r == c { @@ -2714,7 +2712,7 @@ Output: */ split_multi_iterate :: proc(it: ^string, substrs: []string) -> (res: string, ok: bool) #no_bounds_check { - if it == nil || len(it) == 0 || len(substrs) <= 0 { + if len(it) == 0 || len(substrs) <= 0 { return } diff --git a/core/sync/chan/chan.odin b/core/sync/chan/chan.odin index cbcfdf3bf..0c98124de 100644 --- a/core/sync/chan/chan.odin +++ b/core/sync/chan/chan.odin @@ -75,6 +75,7 @@ create_raw_unbuffered :: proc(#any_int msg_size, msg_alignment: int, allocator: ptr := mem.alloc(size, align, allocator) or_return c = (^Raw_Chan)(ptr) + c.allocator = allocator c.allocation_size = size c.unbuffered_data = ([^]byte)(ptr)[offset:] c.msg_size = u16(msg_size) @@ -99,6 +100,7 @@ create_raw_buffered :: proc(#any_int msg_size, msg_alignment: int, #any_int cap: ptr := mem.alloc(size, align, allocator) or_return c = (^Raw_Chan)(ptr) + c.allocator = allocator c.allocation_size = size bptr := ([^]byte)(ptr) @@ -302,7 +304,7 @@ try_recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> bool { if sync.atomic_load(&c.closed) || sync.atomic_load(&c.w_waiting) == 0 { - return false + return false } mem.copy(msg_out, c.unbuffered_data, int(c.msg_size)) @@ -474,10 +476,7 @@ select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: [] return } - r: ^rand.Rand = nil - - - select_idx = rand.int_max(count, r) if count > 0 else 0 + select_idx = rand.int_max(count) if count > 0 else 0 sel := candidates[select_idx] if sel.is_recv { diff --git a/core/sync/extended.odin b/core/sync/extended.odin index 76b7686fe..781ed816e 100644 --- a/core/sync/extended.odin +++ b/core/sync/extended.odin @@ -433,7 +433,7 @@ One_Shot_Event :: struct #no_copy { // Blocks the current thread until the event is made available with `one_shot_event_signal`. one_shot_event_wait :: proc "contextless" (e: ^One_Shot_Event) { for atomic_load_explicit(&e.state, .Acquire) == 0 { - futex_wait(&e.state, 1) + futex_wait(&e.state, 0) } } diff --git a/core/sync/futex_darwin.odin b/core/sync/futex_darwin.odin index 6ea177d1b..fca9aadfe 100644 --- a/core/sync/futex_darwin.odin +++ b/core/sync/futex_darwin.odin @@ -52,7 +52,7 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati } } else { - timeout_ns := u32(duration) * 1000 + timeout_ns := u32(duration) s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns) if s >= 0 { return true diff --git a/core/sync/futex_netbsd.odin b/core/sync/futex_netbsd.odin new file mode 100644 index 000000000..d12409f32 --- /dev/null +++ b/core/sync/futex_netbsd.odin @@ -0,0 +1,74 @@ +//+private +package sync + +import "base:intrinsics" +import "core:time" +import "core:c" +import "core:sys/unix" + +foreign import libc "system:c" + +FUTEX_PRIVATE_FLAG :: 128 + +FUTEX_WAIT_PRIVATE :: 0 | FUTEX_PRIVATE_FLAG +FUTEX_WAKE_PRIVATE :: 1 | FUTEX_PRIVATE_FLAG + +EINTR :: 4 /* Interrupted system call */ +EAGAIN :: 35 /* Resource temporarily unavailable */ +ETIMEDOUT :: 60 /* Operation timed out */ + +Time_Spec :: struct { + time_sec: uint, + time_nsec: uint, +} + +get_last_error :: proc "contextless" () -> int { + foreign libc { + __errno :: proc() -> ^c.int --- + } + return int(__errno()^) +} + +_futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool { + if error, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), 0, 0, 0); !ok { + switch error { + case EINTR, EAGAIN: + return true + case: + _panic("futex_wait failure") + } + } + return true +} + +_futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, duration: time.Duration) -> bool { + if duration <= 0 { + return false + } + if error, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), cast(uintptr) &Time_Spec{ + time_sec = cast(uint)(duration / 1e9), + time_nsec = cast(uint)(duration % 1e9), + }, 0, 0); !ok { + switch error { + case EINTR, EAGAIN: + return true + case ETIMEDOUT: + return false + case: + _panic("futex_wait_with_timeout failure") + } + } + return true +} + +_futex_signal :: proc "contextless" (futex: ^Futex) { + if _, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, 1, 0, 0, 0); !ok { + _panic("futex_wake_single failure") + } +} + +_futex_broadcast :: proc "contextless" (futex: ^Futex) { + if _, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, uintptr(max(i32)), 0, 0, 0); !ok { + _panic("_futex_wake_all failure") + } +} diff --git a/core/sync/futex_wasm.odin b/core/sync/futex_wasm.odin index de1013364..de88e8198 100644 --- a/core/sync/futex_wasm.odin +++ b/core/sync/futex_wasm.odin @@ -5,31 +5,49 @@ package sync import "base:intrinsics" import "core:time" +// NOTE: because `core:sync` is in the dependency chain of a lot of the core packages (mostly through `core:mem`) +// without actually calling into it much, I opted for a runtime panic instead of a compile error here. + _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { - s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1) - return s != 0 + when !intrinsics.has_target_feature("atomics") { + _panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + } else { + s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1) + return s != 0 + } } _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool { - s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, i64(duration)) - return s != 0 - + when !intrinsics.has_target_feature("atomics") { + _panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + } else { + s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, i64(duration)) + return s != 0 + } } _futex_signal :: proc "contextless" (f: ^Futex) { - loop: for { - s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1) - if s >= 1 { - return + when !intrinsics.has_target_feature("atomics") { + _panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + } else { + loop: for { + s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1) + if s >= 1 { + return + } } } } _futex_broadcast :: proc "contextless" (f: ^Futex) { - loop: for { - s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), ~u32(0)) - if s >= 0 { - return + when !intrinsics.has_target_feature("atomics") { + _panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + } else { + loop: for { + s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), ~u32(0)) + if s >= 0 { + return + } } } } diff --git a/core/sync/primitives_atomic.odin b/core/sync/primitives_atomic.odin index 1b7cdfe35..2cf25ac11 100644 --- a/core/sync/primitives_atomic.odin +++ b/core/sync/primitives_atomic.odin @@ -169,7 +169,7 @@ atomic_rw_mutex_shared_unlock :: proc "contextless" (rw: ^Atomic_RW_Mutex) { if (state & Atomic_RW_Mutex_State_Reader_Mask == Atomic_RW_Mutex_State_Reader) && (state & Atomic_RW_Mutex_State_Is_Writing != 0) { - atomic_sema_post(&rw.sema) + atomic_sema_post(&rw.sema) } } diff --git a/core/sync/primitives_netbsd.odin b/core/sync/primitives_netbsd.odin new file mode 100644 index 000000000..594f2ff5c --- /dev/null +++ b/core/sync/primitives_netbsd.odin @@ -0,0 +1,12 @@ +//+private +package sync + +foreign import libc "system:c" + +foreign libc { + _lwp_self :: proc "c" () -> i32 --- +} + +_current_thread_id :: proc "contextless" () -> int { + return int(_lwp_self()) +} diff --git a/core/sys/darwin/CoreFoundation/CFString.odin b/core/sys/darwin/CoreFoundation/CFString.odin index 4a167c604..6ad3c5bfc 100644 --- a/core/sys/darwin/CoreFoundation/CFString.odin +++ b/core/sys/darwin/CoreFoundation/CFString.odin @@ -192,7 +192,7 @@ StringCopyToOdinString :: proc( max := StringGetMaximumSizeForEncoding(length, StringEncoding(StringBuiltInEncodings.UTF8)) buf, err := make([]byte, max, allocator) - if err != nil do return + if err != nil { return } raw_str := runtime.Raw_String { data = raw_data(buf), diff --git a/core/sys/darwin/Foundation/NSApplication.odin b/core/sys/darwin/Foundation/NSApplication.odin index d332345f9..34221aed6 100644 --- a/core/sys/darwin/Foundation/NSApplication.odin +++ b/core/sys/darwin/Foundation/NSApplication.odin @@ -132,7 +132,7 @@ Application_nextEventMatchingMask :: proc "c" (self: ^Application, mask: EventMa @(objc_type=Application, objc_name="sendEvent") Application_sendEvent :: proc "c" (self: ^Application, event: ^Event) { - msgSend(Event, self, "sendEvent:", event) + msgSend(nil, self, "sendEvent:", event) } @(objc_type=Application, objc_name="updateWindows") Application_updateWindows :: proc "c" (self: ^Application) { diff --git a/core/sys/darwin/Foundation/NSWindow.odin b/core/sys/darwin/Foundation/NSWindow.odin index 7159a7c3a..e6103a58a 100644 --- a/core/sys/darwin/Foundation/NSWindow.odin +++ b/core/sys/darwin/Foundation/NSWindow.odin @@ -712,3 +712,7 @@ Window_setDelegate :: proc "c" (self: ^Window, delegate: ^WindowDelegate) { Window_backingScaleFactor :: proc "c" (self: ^Window) -> Float { return msgSend(Float, self, "backingScaleFactor") } +@(objc_type=Window, objc_name="setWantsLayer") +Window_setWantsLayer :: proc "c" (self: ^Window, ok: BOOL) { + msgSend(nil, self, "setWantsLayer:", ok) +} diff --git a/core/sys/darwin/sync.odin b/core/sys/darwin/sync.odin index c76b30d6b..121d3edef 100644 --- a/core/sys/darwin/sync.odin +++ b/core/sys/darwin/sync.odin @@ -5,9 +5,9 @@ foreign import system "system:System.framework" // #define OS_WAIT_ON_ADDR_AVAILABILITY \ // __API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4)) when ODIN_OS == .Darwin { - when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION > 17_04_00 { + when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION >= 17_04_00 { WAIT_ON_ADDRESS_AVAILABLE :: true - } else when ODIN_MINIMUM_OS_VERSION > 14_04_00 { + } else when ODIN_MINIMUM_OS_VERSION >= 14_04_00 { WAIT_ON_ADDRESS_AVAILABLE :: true } else { WAIT_ON_ADDRESS_AVAILABLE :: false @@ -17,9 +17,6 @@ when ODIN_OS == .Darwin { } os_sync_wait_on_address_flag :: enum u32 { - // This flag should be used as a default flag when no other flags listed below are required. - NONE, - // This flag should be used when synchronizing among multiple processes by // placing the @addr passed to os_sync_wait_on_address and its variants // in a shared memory region. @@ -31,15 +28,12 @@ os_sync_wait_on_address_flag :: enum u32 { // This flag should not be used when synchronizing among multiple threads of // a single process. It allows the kernel to perform performance optimizations // as the @addr is local to the calling process. - SHARED, + SHARED = 0, } -os_sync_wait_on_address_flags :: bit_set[os_sync_wait_on_address_flag; u32] +os_sync_wait_on_address_flags :: distinct bit_set[os_sync_wait_on_address_flag; u32] os_sync_wake_by_address_flag :: enum u32 { - // This flag should be used as a default flag when no other flags listed below are required. - NONE, - // This flag should be used when synchronizing among multiple processes by // placing the @addr passed to os_sync_wake_by_address_any and its variants // in a shared memory region. @@ -51,10 +45,10 @@ os_sync_wake_by_address_flag :: enum u32 { // This flag should not be used when synchronizing among multiple threads of // a single process. It allows the kernel to perform performance optimizations // as the @addr is local the calling process. - SHARED, + SHARED = 0, } -os_sync_wake_by_address_flags :: bit_set[os_sync_wake_by_address_flag; u32] +os_sync_wake_by_address_flags :: distinct bit_set[os_sync_wake_by_address_flag; u32] os_clockid :: enum u32 { MACH_ABSOLUTE_TIME = 32, @@ -283,7 +277,7 @@ foreign system { // and the shared memory specification // (See os_sync_wake_by_address_flags_t). // ENOENT : No waiter(s) found waiting on the @addr. - os_sync_wake_by_address_any :: proc(addr: rawptr, size: uint, flags: os_sync_wait_on_address_flags) -> i32 --- + os_sync_wake_by_address_any :: proc(addr: rawptr, size: uint, flags: os_sync_wake_by_address_flags) -> i32 --- // This function is a variant of os_sync_wake_by_address_any that wakes up all waiters // blocked in os_sync_wait_on_address or its variants. @@ -305,5 +299,5 @@ foreign system { // In the event of an error, returns -1 with errno set to indicate the error. // // This function returns same error codes as returned by os_sync_wait_on_address. - os_sync_wake_by_address_all :: proc(addr: rawptr, size: uint, flags: os_sync_wait_on_address_flags) -> i32 --- + os_sync_wake_by_address_all :: proc(addr: rawptr, size: uint, flags: os_sync_wake_by_address_flags) -> i32 --- } diff --git a/core/sys/darwin/xnu_system_call_helpers.odin b/core/sys/darwin/xnu_system_call_helpers.odin index 753f7f058..7fa59bfe0 100644 --- a/core/sys/darwin/xnu_system_call_helpers.odin +++ b/core/sys/darwin/xnu_system_call_helpers.odin @@ -3,6 +3,10 @@ package darwin import "core:c" import "base:runtime" +// IMPORTANT NOTE: direct syscall usage is not allowed by Apple's review process of apps and should +// be entirely avoided in the builtin Odin collections, these are here for users if they don't +// care about the Apple review process. + // this package uses the sys prefix for the proc names to indicate that these aren't native syscalls but directly call such sys_write_string :: proc (fd: c.int, message: string) -> bool { return syscall_write(fd, raw_data(message), cast(u64)len(message)) diff --git a/core/sys/darwin/xnu_system_call_numbers.odin b/core/sys/darwin/xnu_system_call_numbers.odin index 429d18964..00ab75a15 100644 --- a/core/sys/darwin/xnu_system_call_numbers.odin +++ b/core/sys/darwin/xnu_system_call_numbers.odin @@ -1,558 +1,562 @@ package darwin +// IMPORTANT NOTE: direct syscall usage is not allowed by Apple's review process of apps and should +// be entirely avoided in the builtin Odin collections, these are here for users if they don't +// care about the Apple review process. + unix_offset_syscall :: proc "contextless" (number: System_Call_Number) -> uintptr { - return uintptr(number) + uintptr(0x2000000) + return uintptr(number) + uintptr(0x2000000) } System_Call_Number :: enum uintptr { - /* 0 syscall */ - exit = 1, - fork = 2, - read = 3, - write = 4, - open = 5, - close = 6, - wait4 = 7, - /* 8 old creat */ - link = 9, - unlink = 10, - /* 11 old execv */ - chdir = 12, - fchdir = 13, - mknod = 14, - chmod = 15, - chown = 16, - /* 17 old break */ - getfsstat = 18, - /* 19 old lseek */ - getpid = 20, - /* 21 old mount */ - /* 22 old umount */ - setuid = 23, - getuid = 24, - geteuid = 25, - ptrace = 26, - recvmsg = 27, - sendmsg = 28, - recvfrom = 29, - accept = 30, - getpeername = 31, - getsockname = 32, - access = 33, - chflags = 34, - fchflags = 35, - sync = 36, - kill = 37, - /* 38 old stat */ - getppid = 39, - /* 40 old lstat */ - dup = 41, - pipe = 42, - getegid = 43, - /* 44 old profil */ - /* 45 old ktrace */ - sigaction = 46, - getgid = 47, - sigprocmask = 48, - getlogin = 49, - setlogin = 50, - acct = 51, - sigpending = 52, - sigaltstack = 53, - ioctl = 54, - reboot = 55, - revoke = 56, - symlink = 57, - readlink = 58, - execve = 59, - umask = 60, - chroot = 61, - /* 62 old fstat */ - /* 63 used internally and reserved */ - /* getpagesize = 64, invalid */ - msync = 65, - vfork = 66, - /* 67 old vread */ - /* 68 old vwrite */ - /* 69 old sbrk */ - /* 70 old sstk */ - /* 71 old mmap */ - /* 72 old vadvise */ - munmap = 73, - mprotect = 74, - madvise = 75, - /* 76 old vhangup */ - /* 77 old vlimit */ - mincore = 78, - getgroups = 79, - setgroups = 80, - getpgrp = 81, - setpgid = 82, - setitimer = 83, - /* 84 old wait */ - swapon = 85, - getitimer = 86, - /* 87 old gethostname */ - /* 88 old sethostname */ - getdtablesize = 89, - dup2 = 90, - /* 91 old getdopt */ - fcntl = 92, - select = 93, - /* 94 old setdopt */ - fsync = 95, - setpriority = 96, - socket = 97, - connect = 98, - /* 99 old accept */ - getpriority = 100, - /* 101 old send */ - /* 102 old recv */ - /* 103 old sigreturn */ - bind = 104, - setsockopt = 105, - listen = 106, - /* 107 old vtimes */ - /* 108 old sigvec */ - /* 109 old sigblock */ - /* 110 old sigsetmask */ - sigsuspend = 111, - /* 112 old sigstack */ - /* 113 old recvmsg */ - /* 114 old sendmsg */ - /* 115 old vtrace */ - gettimeofday = 116, - getrusage = 117, - getsockopt = 118, - /* 119 old resuba */ - readv = 120, - writev = 121, - settimeofday = 122, - fchown = 123, - fchmod = 124, - /* 125 old recvfrom */ - setreuid = 126, - setregid = 127, - rename = 128, - /* 129 old truncate */ - /* 130 old ftruncate */ - flock = 131, - mkfifo = 132, - sendto = 133, - shutdown = 134, - socketpair = 135, - mkdir = 136, - rmdir = 137, - utimes = 138, - futimes = 139, - adjtime = 140, - /* 141 old getpeername */ - gethostuuid = 142, - /* 143 old sethostid */ - /* 144 old getrlimit */ - /* 145 old setrlimit */ - /* 146 old killpg */ - setsid = 147, - /* 148 old setquota */ - /* 149 old qquota */ - /* 150 old getsockname */ - getpgid = 151, - setprivexec = 152, - pread = 153, - pwrite = 154, - nfssvc = 155, - /* 156 old getdirentries */ - statfs = 157, - fstatfs = 158, - unmount = 159, - /* 160 old async_daemon */ - getfh = 161, - /* 162 old getdomainname */ - /* 163 old setdomainname */ - /* 164 */ - quotactl = 165, - /* 166 old exportfs */ - mount = 167, - /* 168 old ustat */ - csops = 169, - csops_audittoken = 170, - /* 171 old wait3 */ - /* 172 old rpause */ - waitid = 173, - /* 174 old getdents */ - /* 175 old gc_control */ - /* 176 old add_profil */ - kdebug_typefilter = 177, - kdebug_trace_string = 178, - kdebug_trace64 = 179, - kdebug_trace = 180, - setgid = 181, - setegid = 182, - seteuid = 183, - sigreturn = 184, - /* 185 old chud */ - thread_selfcounts = 186, - fdatasync = 187, - stat = 188, - fstat = 189, - lstat = 190, - pathconf = 191, - fpathconf = 192, - /* 193 old getfsstat */ - getrlimit = 194, - setrlimit = 195, - getdirentries = 196, - mmap = 197, - /* 198 old __syscall */ - lseek = 199, - truncate = 200, - ftruncate = 201, - sysctl = 202, - mlock = 203, - munlock = 204, - undelete = 205, - /* 206 old ATsocket */ - /* 207 old ATgetmsg */ - /* 208 old ATputmsg */ - /* 209 old ATsndreq */ - /* 210 old ATsndrsp */ - /* 211 old ATgetreq */ - /* 212 old ATgetrsp */ - /* 213 Reserved for AppleTalk */ - /* 214 */ - /* 215 */ - open_dprotected_np = 216, - fsgetpath_ext = 217, - /* 218 old lstatv */ - /* 219 old fstatv */ - getattrlist = 220, - setattrlist = 221, - getdirentriesattr = 222, - exchangedata = 223, - /* 224 old checkuseraccess or fsgetpath */ - searchfs = 225, - delete = 226, - copyfile = 227, - fgetattrlist = 228, - fsetattrlist = 229, - poll = 230, - /* 231 old watchevent */ - /* 232 old waitevent */ - /* 233 old modwatch */ - getxattr = 234, - fgetxattr = 235, - setxattr = 236, - fsetxattr = 237, - removexattr = 238, - fremovexattr = 239, - listxattr = 240, - flistxattr = 241, - fsctl = 242, - initgroups = 243, - posix_spawn = 244, - ffsctl = 245, - /* 246 */ - nfsclnt = 247, - fhopen = 248, - /* 249 */ - minherit = 250, - semsys = 251, - msgsys = 252, - shmsys = 253, - semctl = 254, - semget = 255, - semop = 256, - /* 257 old semconfig */ - msgctl = 258, - msgget = 259, - msgsnd = 260, - msgrcv = 261, - shmat = 262, - shmctl = 263, - shmdt = 264, - shmget = 265, - shm_open = 266, - shm_unlink = 267, - sem_open = 268, - sem_close = 269, - sem_unlink = 270, - sem_wait = 271, - sem_trywait = 272, - sem_post = 273, - sysctlbyname = 274, - /* 275 old sem_init */ - /* 276 old sem_destroy */ - open_extended = 277, - umask_extended = 278, - stat_extended = 279, - lstat_extended = 280, - fstat_extended = 281, - chmod_extended = 282, - fchmod_extended = 283, - access_extended = 284, - settid = 285, - gettid = 286, - setsgroups = 287, - getsgroups = 288, - setwgroups = 289, - getwgroups = 290, - mkfifo_extended = 291, - mkdir_extended = 292, - identitysvc = 293, - shared_region_check_np = 294, - /* 295 old shared_region_map_np */ - vm_pressure_monitor = 296, - psynch_rw_longrdlock = 297, - psynch_rw_yieldwrlock = 298, - psynch_rw_downgrade = 299, - psynch_rw_upgrade = 300, - psynch_mutexwait = 301, - psynch_mutexdrop = 302, - psynch_cvbroad = 303, - psynch_cvsignal = 304, - psynch_cvwait = 305, - psynch_rw_rdlock = 306, - psynch_rw_wrlock = 307, - psynch_rw_unlock = 308, - psynch_rw_unlock2 = 309, - getsid = 310, - settid_with_pid = 311, - psynch_cvclrprepost = 312, - aio_fsync = 313, - aio_return = 314, - aio_suspend = 315, - aio_cancel = 316, - aio_error = 317, - aio_read = 318, - aio_write = 319, - lio_listio = 320, - /* 321 old __pthread_cond_wait */ - iopolicysys = 322, - process_policy = 323, - mlockall = 324, - munlockall = 325, - /* 326 */ - issetugid = 327, - __pthread_kill = 328, - __pthread_sigmask = 329, - __sigwait = 330, - __disable_threadsignal = 331, - __pthread_markcancel = 332, - __pthread_canceled = 333, - __semwait_signal = 334, - /* 335 old utrace */ - proc_info = 336, - sendfile = 337, - stat64 = 338, - fstat64 = 339, - lstat64 = 340, - stat64_extended = 341, - lstat64_extended = 342, - fstat64_extended = 343, - getdirentries64 = 344, - statfs64 = 345, - fstatfs64 = 346, - getfsstat64 = 347, - __pthread_chdir = 348, - __pthread_fchdir = 349, - audit = 350, - auditon = 351, - /* 352 */ - getauid = 353, - setauid = 354, - /* 355 old getaudit */ - /* 356 old setaudit */ - getaudit_addr = 357, - setaudit_addr = 358, - auditctl = 359, - bsdthread_create = 360, - bsdthread_terminate = 361, - kqueue = 362, - kevent = 363, - lchown = 364, - /* 365 old stack_snapshot */ - bsdthread_register = 366, - workq_open = 367, - workq_kernreturn = 368, - kevent64 = 369, - __old_semwait_signal = 370, - __old_semwait_signal_nocancel = 371, - thread_selfid = 372, - ledger = 373, - kevent_qos = 374, - kevent_id = 375, - /* 376 */ - /* 377 */ - /* 378 */ - /* 379 */ - __mac_execve = 380, - __mac_syscall = 381, - __mac_get_file = 382, - __mac_set_file = 383, - __mac_get_link = 384, - __mac_set_link = 385, - __mac_get_proc = 386, - __mac_set_proc = 387, - __mac_get_fd = 388, - __mac_set_fd = 389, - __mac_get_pid = 390, - /* 391 */ - /* 392 */ - /* 393 */ - pselect = 394, - pselect_nocancel = 395, - read_nocancel = 396, - write_nocancel = 397, - open_nocancel = 398, - close_nocancel = 399, - wait4_nocancel = 400, - recvmsg_nocancel = 401, - sendmsg_nocancel = 402, - recvfrom_nocancel = 403, - accept_nocancel = 404, - msync_nocancel = 405, - fcntl_nocancel = 406, - select_nocancel = 407, - fsync_nocancel = 408, - connect_nocancel = 409, - sigsuspend_nocancel = 410, - readv_nocancel = 411, - writev_nocancel = 412, - sendto_nocancel = 413, - pread_nocancel = 414, - pwrite_nocancel = 415, - waitid_nocancel = 416, - poll_nocancel = 417, - msgsnd_nocancel = 418, - msgrcv_nocancel = 419, - sem_wait_nocancel = 420, - aio_suspend_nocancel = 421, - __sigwait_nocancel = 422, - __semwait_signal_nocancel = 423, - __mac_mount = 424, - __mac_get_mount = 425, - __mac_getfsstat = 426, - fsgetpath = 427, - audit_session_self = 428, - audit_session_join = 429, - fileport_makeport = 430, - fileport_makefd = 431, - audit_session_port = 432, - pid_suspend = 433, - pid_resume = 434, - pid_hibernate = 435, - pid_shutdown_sockets = 436, - /* 437 old shared_region_slide_np */ - shared_region_map_and_slide_np = 438, - kas_info = 439, - memorystatus_control = 440, - guarded_open_np = 441, - guarded_close_np = 442, - guarded_kqueue_np = 443, - change_fdguard_np = 444, - usrctl = 445, - proc_rlimit_control = 446, - connectx = 447, - disconnectx = 448, - peeloff = 449, - socket_delegate = 450, - telemetry = 451, - proc_uuid_policy = 452, - memorystatus_get_level = 453, - system_override = 454, - vfs_purge = 455, - sfi_ctl = 456, - sfi_pidctl = 457, - coalition = 458, - coalition_info = 459, - necp_match_policy = 460, - getattrlistbulk = 461, - clonefileat = 462, - openat = 463, - openat_nocancel = 464, - renameat = 465, - faccessat = 466, - fchmodat = 467, - fchownat = 468, - fstatat = 469, - fstatat64 = 470, - linkat = 471, - unlinkat = 472, - readlinkat = 473, - symlinkat = 474, - mkdirat = 475, - getattrlistat = 476, - proc_trace_log = 477, - bsdthread_ctl = 478, - openbyid_np = 479, - recvmsg_x = 480, - sendmsg_x = 481, - thread_selfusage = 482, - csrctl = 483, - guarded_open_dprotected_np = 484, - guarded_write_np = 485, - guarded_pwrite_np = 486, - guarded_writev_np = 487, - renameatx_np = 488, - mremap_encrypted = 489, - netagent_trigger = 490, - stack_snapshot_with_config = 491, - microstackshot = 492, - grab_pgo_data = 493, - persona = 494, - /* 495 */ - mach_eventlink_signal = 496, - mach_eventlink_wait_until = 497, - mach_eventlink_signal_wait_until = 498, - work_interval_ctl = 499, - getentropy = 500, - necp_open = 501, - necp_client_action = 502, - nexus_open = 503, // for those who are intressted http://newosxbook.com/bonus/vol1ch16.html - nexus_register = 504, - nexus_deregister = 505, - nexus_create = 506, - nexus_destroy = 507, - nexus_get_opt = 508, - nexus_set_opt = 509, - channel_open = 510, - channel_get_info = 511, - channel_sync = 512, - channel_get_opt = 513, - channel_set_opt = 514, - ulock_wait = 515, - ulock_wake = 516, - fclonefileat = 517, - fs_snapshot = 518, - register_uexc_handler = 519, - terminate_with_payload = 520, - abort_with_payload = 521, - necp_session_open = 522, - necp_session_action = 523, - setattrlistat = 524, - net_qos_guideline = 525, - fmount = 526, - ntp_adjtime = 527, - ntp_gettime = 528, - os_fault_with_payload = 529, - kqueue_workloop_ctl = 530, - mach_bridge_remote_time = 531, - coalition_ledger = 532, - log_data = 533, - memorystatus_available_memory = 534, - objc_bp_assist_cfg_np = 535, - shared_region_map_and_slide_2_np = 536, - pivot_root = 537, - task_inspect_for_pid = 538, - task_read_for_pid = 539, - preadv = 540, - pwritev = 541, - preadv_nocancel = 542, - pwritev_nocancel = 543, - ulock_wait2 = 544, - proc_info_extended_id = 545, - tracker_action = 546, - debug_syscall_reject = 547, - MAXSYSCALL = 548, - /* invalid = 63, */ + /* 0 syscall */ + exit = 1, + fork = 2, + read = 3, + write = 4, + open = 5, + close = 6, + wait4 = 7, + /* 8 old creat */ + link = 9, + unlink = 10, + /* 11 old execv */ + chdir = 12, + fchdir = 13, + mknod = 14, + chmod = 15, + chown = 16, + /* 17 old break */ + getfsstat = 18, + /* 19 old lseek */ + getpid = 20, + /* 21 old mount */ + /* 22 old umount */ + setuid = 23, + getuid = 24, + geteuid = 25, + ptrace = 26, + recvmsg = 27, + sendmsg = 28, + recvfrom = 29, + accept = 30, + getpeername = 31, + getsockname = 32, + access = 33, + chflags = 34, + fchflags = 35, + sync = 36, + kill = 37, + /* 38 old stat */ + getppid = 39, + /* 40 old lstat */ + dup = 41, + pipe = 42, + getegid = 43, + /* 44 old profil */ + /* 45 old ktrace */ + sigaction = 46, + getgid = 47, + sigprocmask = 48, + getlogin = 49, + setlogin = 50, + acct = 51, + sigpending = 52, + sigaltstack = 53, + ioctl = 54, + reboot = 55, + revoke = 56, + symlink = 57, + readlink = 58, + execve = 59, + umask = 60, + chroot = 61, + /* 62 old fstat */ + /* 63 used internally and reserved */ + /* getpagesize = 64, invalid */ + msync = 65, + vfork = 66, + /* 67 old vread */ + /* 68 old vwrite */ + /* 69 old sbrk */ + /* 70 old sstk */ + /* 71 old mmap */ + /* 72 old vadvise */ + munmap = 73, + mprotect = 74, + madvise = 75, + /* 76 old vhangup */ + /* 77 old vlimit */ + mincore = 78, + getgroups = 79, + setgroups = 80, + getpgrp = 81, + setpgid = 82, + setitimer = 83, + /* 84 old wait */ + swapon = 85, + getitimer = 86, + /* 87 old gethostname */ + /* 88 old sethostname */ + getdtablesize = 89, + dup2 = 90, + /* 91 old getdopt */ + fcntl = 92, + select = 93, + /* 94 old setdopt */ + fsync = 95, + setpriority = 96, + socket = 97, + connect = 98, + /* 99 old accept */ + getpriority = 100, + /* 101 old send */ + /* 102 old recv */ + /* 103 old sigreturn */ + bind = 104, + setsockopt = 105, + listen = 106, + /* 107 old vtimes */ + /* 108 old sigvec */ + /* 109 old sigblock */ + /* 110 old sigsetmask */ + sigsuspend = 111, + /* 112 old sigstack */ + /* 113 old recvmsg */ + /* 114 old sendmsg */ + /* 115 old vtrace */ + gettimeofday = 116, + getrusage = 117, + getsockopt = 118, + /* 119 old resuba */ + readv = 120, + writev = 121, + settimeofday = 122, + fchown = 123, + fchmod = 124, + /* 125 old recvfrom */ + setreuid = 126, + setregid = 127, + rename = 128, + /* 129 old truncate */ + /* 130 old ftruncate */ + flock = 131, + mkfifo = 132, + sendto = 133, + shutdown = 134, + socketpair = 135, + mkdir = 136, + rmdir = 137, + utimes = 138, + futimes = 139, + adjtime = 140, + /* 141 old getpeername */ + gethostuuid = 142, + /* 143 old sethostid */ + /* 144 old getrlimit */ + /* 145 old setrlimit */ + /* 146 old killpg */ + setsid = 147, + /* 148 old setquota */ + /* 149 old qquota */ + /* 150 old getsockname */ + getpgid = 151, + setprivexec = 152, + pread = 153, + pwrite = 154, + nfssvc = 155, + /* 156 old getdirentries */ + statfs = 157, + fstatfs = 158, + unmount = 159, + /* 160 old async_daemon */ + getfh = 161, + /* 162 old getdomainname */ + /* 163 old setdomainname */ + /* 164 */ + quotactl = 165, + /* 166 old exportfs */ + mount = 167, + /* 168 old ustat */ + csops = 169, + csops_audittoken = 170, + /* 171 old wait3 */ + /* 172 old rpause */ + waitid = 173, + /* 174 old getdents */ + /* 175 old gc_control */ + /* 176 old add_profil */ + kdebug_typefilter = 177, + kdebug_trace_string = 178, + kdebug_trace64 = 179, + kdebug_trace = 180, + setgid = 181, + setegid = 182, + seteuid = 183, + sigreturn = 184, + /* 185 old chud */ + thread_selfcounts = 186, + fdatasync = 187, + stat = 188, + fstat = 189, + lstat = 190, + pathconf = 191, + fpathconf = 192, + /* 193 old getfsstat */ + getrlimit = 194, + setrlimit = 195, + getdirentries = 196, + mmap = 197, + /* 198 old __syscall */ + lseek = 199, + truncate = 200, + ftruncate = 201, + sysctl = 202, + mlock = 203, + munlock = 204, + undelete = 205, + /* 206 old ATsocket */ + /* 207 old ATgetmsg */ + /* 208 old ATputmsg */ + /* 209 old ATsndreq */ + /* 210 old ATsndrsp */ + /* 211 old ATgetreq */ + /* 212 old ATgetrsp */ + /* 213 Reserved for AppleTalk */ + /* 214 */ + /* 215 */ + open_dprotected_np = 216, + fsgetpath_ext = 217, + /* 218 old lstatv */ + /* 219 old fstatv */ + getattrlist = 220, + setattrlist = 221, + getdirentriesattr = 222, + exchangedata = 223, + /* 224 old checkuseraccess or fsgetpath */ + searchfs = 225, + delete = 226, + copyfile = 227, + fgetattrlist = 228, + fsetattrlist = 229, + poll = 230, + /* 231 old watchevent */ + /* 232 old waitevent */ + /* 233 old modwatch */ + getxattr = 234, + fgetxattr = 235, + setxattr = 236, + fsetxattr = 237, + removexattr = 238, + fremovexattr = 239, + listxattr = 240, + flistxattr = 241, + fsctl = 242, + initgroups = 243, + posix_spawn = 244, + ffsctl = 245, + /* 246 */ + nfsclnt = 247, + fhopen = 248, + /* 249 */ + minherit = 250, + semsys = 251, + msgsys = 252, + shmsys = 253, + semctl = 254, + semget = 255, + semop = 256, + /* 257 old semconfig */ + msgctl = 258, + msgget = 259, + msgsnd = 260, + msgrcv = 261, + shmat = 262, + shmctl = 263, + shmdt = 264, + shmget = 265, + shm_open = 266, + shm_unlink = 267, + sem_open = 268, + sem_close = 269, + sem_unlink = 270, + sem_wait = 271, + sem_trywait = 272, + sem_post = 273, + sysctlbyname = 274, + /* 275 old sem_init */ + /* 276 old sem_destroy */ + open_extended = 277, + umask_extended = 278, + stat_extended = 279, + lstat_extended = 280, + fstat_extended = 281, + chmod_extended = 282, + fchmod_extended = 283, + access_extended = 284, + settid = 285, + gettid = 286, + setsgroups = 287, + getsgroups = 288, + setwgroups = 289, + getwgroups = 290, + mkfifo_extended = 291, + mkdir_extended = 292, + identitysvc = 293, + shared_region_check_np = 294, + /* 295 old shared_region_map_np */ + vm_pressure_monitor = 296, + psynch_rw_longrdlock = 297, + psynch_rw_yieldwrlock = 298, + psynch_rw_downgrade = 299, + psynch_rw_upgrade = 300, + psynch_mutexwait = 301, + psynch_mutexdrop = 302, + psynch_cvbroad = 303, + psynch_cvsignal = 304, + psynch_cvwait = 305, + psynch_rw_rdlock = 306, + psynch_rw_wrlock = 307, + psynch_rw_unlock = 308, + psynch_rw_unlock2 = 309, + getsid = 310, + settid_with_pid = 311, + psynch_cvclrprepost = 312, + aio_fsync = 313, + aio_return = 314, + aio_suspend = 315, + aio_cancel = 316, + aio_error = 317, + aio_read = 318, + aio_write = 319, + lio_listio = 320, + /* 321 old __pthread_cond_wait */ + iopolicysys = 322, + process_policy = 323, + mlockall = 324, + munlockall = 325, + /* 326 */ + issetugid = 327, + __pthread_kill = 328, + __pthread_sigmask = 329, + __sigwait = 330, + __disable_threadsignal = 331, + __pthread_markcancel = 332, + __pthread_canceled = 333, + __semwait_signal = 334, + /* 335 old utrace */ + proc_info = 336, + sendfile = 337, + stat64 = 338, + fstat64 = 339, + lstat64 = 340, + stat64_extended = 341, + lstat64_extended = 342, + fstat64_extended = 343, + getdirentries64 = 344, + statfs64 = 345, + fstatfs64 = 346, + getfsstat64 = 347, + __pthread_chdir = 348, + __pthread_fchdir = 349, + audit = 350, + auditon = 351, + /* 352 */ + getauid = 353, + setauid = 354, + /* 355 old getaudit */ + /* 356 old setaudit */ + getaudit_addr = 357, + setaudit_addr = 358, + auditctl = 359, + bsdthread_create = 360, + bsdthread_terminate = 361, + kqueue = 362, + kevent = 363, + lchown = 364, + /* 365 old stack_snapshot */ + bsdthread_register = 366, + workq_open = 367, + workq_kernreturn = 368, + kevent64 = 369, + __old_semwait_signal = 370, + __old_semwait_signal_nocancel = 371, + thread_selfid = 372, + ledger = 373, + kevent_qos = 374, + kevent_id = 375, + /* 376 */ + /* 377 */ + /* 378 */ + /* 379 */ + __mac_execve = 380, + __mac_syscall = 381, + __mac_get_file = 382, + __mac_set_file = 383, + __mac_get_link = 384, + __mac_set_link = 385, + __mac_get_proc = 386, + __mac_set_proc = 387, + __mac_get_fd = 388, + __mac_set_fd = 389, + __mac_get_pid = 390, + /* 391 */ + /* 392 */ + /* 393 */ + pselect = 394, + pselect_nocancel = 395, + read_nocancel = 396, + write_nocancel = 397, + open_nocancel = 398, + close_nocancel = 399, + wait4_nocancel = 400, + recvmsg_nocancel = 401, + sendmsg_nocancel = 402, + recvfrom_nocancel = 403, + accept_nocancel = 404, + msync_nocancel = 405, + fcntl_nocancel = 406, + select_nocancel = 407, + fsync_nocancel = 408, + connect_nocancel = 409, + sigsuspend_nocancel = 410, + readv_nocancel = 411, + writev_nocancel = 412, + sendto_nocancel = 413, + pread_nocancel = 414, + pwrite_nocancel = 415, + waitid_nocancel = 416, + poll_nocancel = 417, + msgsnd_nocancel = 418, + msgrcv_nocancel = 419, + sem_wait_nocancel = 420, + aio_suspend_nocancel = 421, + __sigwait_nocancel = 422, + __semwait_signal_nocancel = 423, + __mac_mount = 424, + __mac_get_mount = 425, + __mac_getfsstat = 426, + fsgetpath = 427, + audit_session_self = 428, + audit_session_join = 429, + fileport_makeport = 430, + fileport_makefd = 431, + audit_session_port = 432, + pid_suspend = 433, + pid_resume = 434, + pid_hibernate = 435, + pid_shutdown_sockets = 436, + /* 437 old shared_region_slide_np */ + shared_region_map_and_slide_np = 438, + kas_info = 439, + memorystatus_control = 440, + guarded_open_np = 441, + guarded_close_np = 442, + guarded_kqueue_np = 443, + change_fdguard_np = 444, + usrctl = 445, + proc_rlimit_control = 446, + connectx = 447, + disconnectx = 448, + peeloff = 449, + socket_delegate = 450, + telemetry = 451, + proc_uuid_policy = 452, + memorystatus_get_level = 453, + system_override = 454, + vfs_purge = 455, + sfi_ctl = 456, + sfi_pidctl = 457, + coalition = 458, + coalition_info = 459, + necp_match_policy = 460, + getattrlistbulk = 461, + clonefileat = 462, + openat = 463, + openat_nocancel = 464, + renameat = 465, + faccessat = 466, + fchmodat = 467, + fchownat = 468, + fstatat = 469, + fstatat64 = 470, + linkat = 471, + unlinkat = 472, + readlinkat = 473, + symlinkat = 474, + mkdirat = 475, + getattrlistat = 476, + proc_trace_log = 477, + bsdthread_ctl = 478, + openbyid_np = 479, + recvmsg_x = 480, + sendmsg_x = 481, + thread_selfusage = 482, + csrctl = 483, + guarded_open_dprotected_np = 484, + guarded_write_np = 485, + guarded_pwrite_np = 486, + guarded_writev_np = 487, + renameatx_np = 488, + mremap_encrypted = 489, + netagent_trigger = 490, + stack_snapshot_with_config = 491, + microstackshot = 492, + grab_pgo_data = 493, + persona = 494, + /* 495 */ + mach_eventlink_signal = 496, + mach_eventlink_wait_until = 497, + mach_eventlink_signal_wait_until = 498, + work_interval_ctl = 499, + getentropy = 500, + necp_open = 501, + necp_client_action = 502, + nexus_open = 503, // for those who are intressted http://newosxbook.com/bonus/vol1ch16.html + nexus_register = 504, + nexus_deregister = 505, + nexus_create = 506, + nexus_destroy = 507, + nexus_get_opt = 508, + nexus_set_opt = 509, + channel_open = 510, + channel_get_info = 511, + channel_sync = 512, + channel_get_opt = 513, + channel_set_opt = 514, + ulock_wait = 515, + ulock_wake = 516, + fclonefileat = 517, + fs_snapshot = 518, + register_uexc_handler = 519, + terminate_with_payload = 520, + abort_with_payload = 521, + necp_session_open = 522, + necp_session_action = 523, + setattrlistat = 524, + net_qos_guideline = 525, + fmount = 526, + ntp_adjtime = 527, + ntp_gettime = 528, + os_fault_with_payload = 529, + kqueue_workloop_ctl = 530, + mach_bridge_remote_time = 531, + coalition_ledger = 532, + log_data = 533, + memorystatus_available_memory = 534, + objc_bp_assist_cfg_np = 535, + shared_region_map_and_slide_2_np = 536, + pivot_root = 537, + task_inspect_for_pid = 538, + task_read_for_pid = 539, + preadv = 540, + pwritev = 541, + preadv_nocancel = 542, + pwritev_nocancel = 543, + ulock_wait2 = 544, + proc_info_extended_id = 545, + tracker_action = 546, + debug_syscall_reject = 547, + MAXSYSCALL = 548, + /* invalid = 63, */ } diff --git a/core/sys/darwin/xnu_system_call_wrappers.odin b/core/sys/darwin/xnu_system_call_wrappers.odin index b69877cc9..d289ee7c1 100644 --- a/core/sys/darwin/xnu_system_call_wrappers.odin +++ b/core/sys/darwin/xnu_system_call_wrappers.odin @@ -3,6 +3,10 @@ package darwin import "core:c" import "base:intrinsics" +// IMPORTANT NOTE: direct syscall usage is not allowed by Apple's review process of apps and should +// be entirely avoided in the builtin Odin collections, these are here for users if they don't +// care about the Apple review process. + /* flock */ LOCK_SH :: 1 /* shared lock */ LOCK_EX :: 2 /* exclusive lock */ @@ -337,7 +341,7 @@ syscall_ftruncate :: #force_inline proc "contextless" (fd: c.int, length: off_t) return cast(c.int)intrinsics.syscall(unix_offset_syscall(.ftruncate), uintptr(fd), uintptr(length)) } -syscall_sysctl :: #force_inline proc "contextless" (name: ^c.int, namelen: c.uint, oldp: rawptr, oldlenp: ^i64, newp: ^i8, newlen: i64) -> c.int { +syscall_sysctl :: #force_inline proc "contextless" (name: [^]c.int, namelen: c.size_t, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> c.int { return cast(c.int)intrinsics.syscall(unix_offset_syscall(.sysctl), uintptr(name), uintptr(namelen), uintptr(oldp), uintptr(oldlenp), uintptr(newp), uintptr(newlen)) } @@ -390,8 +394,8 @@ syscall_adjtime :: #force_inline proc "contextless" (delta: ^timeval, old_delta: return cast(c.int)intrinsics.syscall(unix_offset_syscall(.adjtime), uintptr(delta), uintptr(old_delta)) } -syscall_sysctlbyname :: #force_inline proc "contextless" (name: cstring, oldp: rawptr, oldlenp: ^i64, newp: rawptr, newlen: i64) -> c.int { - return cast(c.int)intrinsics.syscall(unix_offset_syscall(.sysctlbyname), transmute(uintptr)name, uintptr(oldp), uintptr(oldlenp), uintptr(newp), uintptr(newlen)) +syscall_sysctlbyname :: #force_inline proc "contextless" (name: string, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> c.int { + return cast(c.int)intrinsics.syscall(unix_offset_syscall(.sysctlbyname), uintptr(raw_data(name)), uintptr(len(name)), uintptr(oldp), uintptr(oldlenp), uintptr(newp), uintptr(newlen)) } syscall_proc_info :: #force_inline proc "contextless" (num: c.int, pid: u32, flavor: c.int, arg: u64, buffer: rawptr, buffer_size: c.int) -> c.int { diff --git a/core/sys/info/cpu_arm.odin b/core/sys/info/cpu_arm.odin index f66f0e780..aa4bb368a 100644 --- a/core/sys/info/cpu_arm.odin +++ b/core/sys/info/cpu_arm.odin @@ -1,26 +1,70 @@ //+build arm32, arm64 package sysinfo -// TODO: Set up an enum with the ARM equivalent of the above. -CPU_Feature :: enum u64 {} +import "core:sys/unix" -cpu_features: Maybe(CPU_Feature) -cpu_name: Maybe(string) +_ :: unix -@(init, private) -init_cpu_features :: proc "c" () { +CPU_Feature :: enum u64 { + // Advanced SIMD & floating-point capabilities: + asimd, // General support for Advanced SIMD instructions/neon. + floatingpoint, // General support for floating-point instructions. + asimdhp, // Advanced SIMD half-precision conversion instructions. + bf16, // Storage and arithmetic instructions of the Brain Floating Point (BFloat16) data type. + fcma, // Floating-point complex number instructions. + fhm, // Floating-point half-precision multiplication instructions. + fp16, // General half-precision floating-point data processing instructions. + frint, // Floating-point to integral valued floating-point number rounding instructions. + i8mm, // Advanced SIMD int8 matrix multiplication instructions. + jscvt, // JavaScript conversion instruction. + rdm, // Advanced SIMD rounding double multiply accumulate instructions. + + flagm, // Condition flag manipulation instructions. + flagm2, // Enhancements to condition flag manipulation instructions. + crc32, // CRC32 instructions. + + lse, // Atomic instructions to support large systems. + lse2, // Changes to single-copy atomicity and alignment requirements for loads and stores for large systems. + lrcpc, // Load-acquire Release Consistency processor consistent (RCpc) instructions. + lrcpc2, // Load-acquire Release Consistency processor consistent (RCpc) instructions version 2. + + aes, + pmull, + sha1, + sha256, + sha512, + sha3, + + sb, // Barrier instruction to control speculation. + ssbs, // Instructions to control speculation of loads and stores. } +CPU_Features :: distinct bit_set[CPU_Feature; u64] + +cpu_features: Maybe(CPU_Features) +cpu_name: Maybe(string) + @(private) -_cpu_name_buf: [72]u8 +cpu_name_buf: [128]byte @(init, private) -init_cpu_name :: proc "c" () { - when ODIN_ARCH == .arm32 { - copy(_cpu_name_buf[:], "ARM") - cpu_name = string(_cpu_name_buf[:3]) - } else { - copy(_cpu_name_buf[:], "ARM64") - cpu_name = string(_cpu_name_buf[:5]) +init_cpu_name :: proc "contextless" () { + generic := true + + when ODIN_OS == .Darwin { + if unix.sysctlbyname("machdep.cpu.brand_string", &cpu_name_buf) { + cpu_name = string(cstring(rawptr(&cpu_name_buf))) + generic = false + } } -} \ No newline at end of file + + if generic { + when ODIN_ARCH == .arm64 { + copy(cpu_name_buf[:], "ARM64") + cpu_name = string(cpu_name_buf[:len("ARM64")]) + } else { + copy(cpu_name_buf[:], "ARM") + cpu_name = string(cpu_name_buf[:len("ARM")]) + } + } +} diff --git a/core/sys/info/cpu_darwin_arm64.odin b/core/sys/info/cpu_darwin_arm64.odin new file mode 100644 index 000000000..ffa60d1cb --- /dev/null +++ b/core/sys/info/cpu_darwin_arm64.odin @@ -0,0 +1,98 @@ +package sysinfo + +import "core:sys/unix" + +@(init, private) +init_cpu_features :: proc "contextless" () { + @(static) features: CPU_Features + defer cpu_features = features + + try_set :: proc "contextless" (name: cstring, feature: CPU_Feature) -> (ok: bool) { + support: b32 + if ok = unix.sysctlbyname(name, &support); ok && support { + features += { feature } + } + return + } + + // Docs from Apple: https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_instruction_set_characteristics + // Features from there that do not have (or I didn't find) an equivalent on Linux are commented out below. + + // Advanced SIMD & floating-point capabilities: + { + if !try_set("hw.optional.AdvSIMD", .asimd) { + try_set("hw.optional.neon", .asimd) + } + + try_set("hw.optional.floatingpoint", .floatingpoint) + + if !try_set("hw.optional.AdvSIMD_HPFPCvt", .asimdhp) { + try_set("hw.optional.neon_hpfp", .asimdhp) + } + + try_set("hw.optional.arm.FEAT_BF16", .bf16) + // try_set("hw.optional.arm.FEAT_DotProd", .dotprod) + + if !try_set("hw.optional.arm.FEAT_FCMA", .fcma) { + try_set("hw.optional.armv8_3_compnum", .fcma) + } + + if !try_set("hw.optional.arm.FEAT_FHM", .fhm) { + try_set("hw.optional.armv8_2_fhm", .fhm) + } + + if !try_set("hw.optional.arm.FEAT_FP16", .fp16) { + try_set("hw.optional.neon_fp16", .fp16) + } + + try_set("hw.optional.arm.FEAT_FRINTTS", .frint) + try_set("hw.optional.arm.FEAT_I8MM", .i8mm) + try_set("hw.optional.arm.FEAT_JSCVT", .jscvt) + try_set("hw.optional.arm.FEAT_RDM", .rdm) + } + + // Integer capabilities: + { + try_set("hw.optional.arm.FEAT_FlagM", .flagm) + try_set("hw.optional.arm.FEAT_FlagM2", .flagm2) + try_set("hw.optional.armv8_crc32", .crc32) + } + + // Atomic and memory ordering instruction capabilities: + { + try_set("hw.optional.arm.FEAT_LRCPC", .lrcpc) + try_set("hw.optional.arm.FEAT_LRCPC2", .lrcpc2) + + if !try_set("hw.optional.arm.FEAT_LSE", .lse) { + try_set("hw.optional.armv8_1_atomics", .lse) + } + + // try_set("hw.optional.arm.FEAT_LSE2", .lse2) + } + + // Encryption capabilities: + { + try_set("hw.optional.arm.FEAT_AES", .aes) + try_set("hw.optional.arm.FEAT_PMULL", .pmull) + try_set("hw.optional.arm.FEAT_SHA1", .sha1) + try_set("hw.optional.arm.FEAT_SHA256", .sha256) + + if !try_set("hw.optional.arm.FEAT_SHA512", .sha512) { + try_set("hw.optional.armv8_2_sha512", .sha512) + } + + if !try_set("hw.optional.arm.FEAT_SHA3", .sha3) { + try_set("hw.optional.armv8_2_sha3", .sha3) + } + } + + // General capabilities: + { + // try_set("hw.optional.arm.FEAT_BTI", .bti) + // try_set("hw.optional.arm.FEAT_DPB", .dpb) + // try_set("hw.optional.arm.FEAT_DPB2", .dpb2) + // try_set("hw.optional.arm.FEAT_ECV", .ecv) + try_set("hw.optional.arm.FEAT_SB", .sb) + try_set("hw.optional.arm.FEAT_SSBS", .ssbs) + } +} diff --git a/core/sys/info/cpu_intel.odin b/core/sys/info/cpu_intel.odin index 2b8f9852f..73d4c15e7 100644 --- a/core/sys/info/cpu_intel.odin +++ b/core/sys/info/cpu_intel.odin @@ -67,8 +67,8 @@ init_cpu_features :: proc "c" () { try_set(&set, .os_xsave, 27, ecx1) try_set(&set, .rdrand, 30, ecx1) - when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { - // xgetbv is an illegal instruction under FreeBSD 13 & OpenBSD 7.1 + when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { + // xgetbv is an illegal instruction under FreeBSD 13, OpenBSD 7.1 and NetBSD 10 // return before probing further cpu_features = set return @@ -117,7 +117,7 @@ init_cpu_name :: proc "c" () { return } - _buf := transmute(^[0x12]u32)&_cpu_name_buf + _buf := (^[0x12]u32)(&_cpu_name_buf) _buf[ 0], _buf[ 1], _buf[ 2], _buf[ 3] = cpuid(0x8000_0002, 0) _buf[ 4], _buf[ 5], _buf[ 6], _buf[ 7] = cpuid(0x8000_0003, 0) _buf[ 8], _buf[ 9], _buf[10], _buf[11] = cpuid(0x8000_0004, 0) @@ -133,4 +133,4 @@ init_cpu_name :: proc "c" () { brand = brand[:len(brand) - 1] } cpu_name = brand -} \ No newline at end of file +} diff --git a/core/sys/info/cpu_linux_arm.odin b/core/sys/info/cpu_linux_arm.odin new file mode 100644 index 000000000..dcc252971 --- /dev/null +++ b/core/sys/info/cpu_linux_arm.odin @@ -0,0 +1,65 @@ +//+build arm32, arm64 +//+build linux +package sysinfo + +import "core:sys/linux" +import "core:strings" + +@(init, private) +init_cpu_features :: proc() { + fd, err := linux.open("/proc/cpuinfo", {}) + 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 } + + features: CPU_Features + defer cpu_features = features + + str := string(buf[:n]) + for line in strings.split_lines_iterator(&str) { + key, _, value := strings.partition(line, ":") + key = strings.trim_space(key) + value = strings.trim_space(value) + + if key != "Features" { continue } + + for feature in strings.split_by_byte_iterator(&value, ' ') { + switch feature { + case "asimd", "neon": features += { .asimd } + case "fp": features += { .floatingpoint } + case "asimdhp": features += { .asimdhp } + case "asimdbf16": features += { .bf16 } + case "fcma": features += { .fcma } + case "asimdfhm": features += { .fhm } + case "fphp", "half": features += { .fp16 } + case "frint": features += { .frint } + case "i8mm": features += { .i8mm } + case "jscvt": features += { .jscvt } + case "asimdrdm": features += { .rdm } + + case "flagm": features += { .flagm } + case "flagm2": features += { .flagm2 } + case "crc32": features += { .crc32 } + + case "atomics": features += { .lse } + case "lrcpc": features += { .lrcpc } + case "ilrcpc": features += { .lrcpc2 } + + case "aes": features += { .aes } + case "pmull": features += { .pmull } + case "sha1": features += { .sha1 } + case "sha2": features += { .sha256 } + case "sha3": features += { .sha3 } + case "sha512": features += { .sha512 } + + case "sb": features += { .sb } + case "ssbs": features += { .ssbs } + } + } + break + } +} diff --git a/core/sys/info/doc.odin b/core/sys/info/doc.odin index 15af0d4b3..802cd9c60 100644 --- a/core/sys/info/doc.odin +++ b/core/sys/info/doc.odin @@ -19,18 +19,21 @@ Example: import si "core:sys/info" main :: proc() { - fmt.printf("Odin: %v\n", ODIN_VERSION) - fmt.printf("OS: %v\n", si.os_version.as_string) - fmt.printf("OS: %#v\n", si.os_version) - fmt.printf("CPU: %v\n", si.cpu_name) - fmt.printf("RAM: %v MiB\n", si.ram.total_ram / 1024 / 1024) + fmt.printfln("Odin: %v", ODIN_VERSION) + fmt.printfln("OS: %v", si.os_version.as_string) + fmt.printfln("OS: %#v", si.os_version) + fmt.printfln("CPU: %v", si.cpu_name) + fmt.printfln("RAM: %#.1M", si.ram.total_ram) + + // fmt.printfln("Features: %v", si.cpu_features) + // fmt.printfln("MacOS version: %v", si.macos_version) fmt.println() for gpu, i in si.gpus { - fmt.printf("GPU #%v:\n", i) - fmt.printf("\tVendor: %v\n", gpu.vendor_name) - fmt.printf("\tModel: %v\n", gpu.model_name) - fmt.printf("\tVRAM: %v MiB\n", gpu.total_ram / 1024 / 1024) + fmt.printfln("GPU #%v:", i) + fmt.printfln("\tVendor: %v", gpu.vendor_name) + fmt.printfln("\tModel: %v", gpu.model_name) + fmt.printfln("\tVRAM: %#.1M", gpu.total_ram) } } @@ -51,11 +54,11 @@ Example: as_string = "Windows 10 Professional (version: 20H2), build: 19042.1466", } CPU: AMD Ryzen 7 1800X Eight-Core Processor - RAM: 65469 MiB + RAM: 64.0 GiB GPU #0: Vendor: Advanced Micro Devices, Inc. Model: Radeon RX Vega - VRAM: 8176 MiB + VRAM: 8.0 GiB - Example macOS output: @@ -73,6 +76,6 @@ Example: as_string = "macOS Monterey 12.4 (build 21F79, kernel 21.5.0)", } CPU: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz - RAM: 8192 MiB + RAM: 8.0 GiB */ package sysinfo diff --git a/core/sys/info/platform_openbsd.odin b/core/sys/info/platform_bsd.odin similarity index 88% rename from core/sys/info/platform_openbsd.odin rename to core/sys/info/platform_bsd.odin index 772531ceb..e2273d253 100644 --- a/core/sys/info/platform_openbsd.odin +++ b/core/sys/info/platform_bsd.odin @@ -1,4 +1,4 @@ -// +build openbsd +//+build openbsd, netbsd package sysinfo import sys "core:sys/unix" @@ -11,12 +11,16 @@ version_string_buf: [1024]u8 @(init, private) init_os_version :: proc () { - os_version.platform = .OpenBSD + when ODIN_OS == .NetBSD { + os_version.platform = .NetBSD + } else { + os_version.platform = .OpenBSD + } kernel_version_buf: [1024]u8 b := strings.builder_from_bytes(version_string_buf[:]) - // Retrieve kernel info using `sysctl`, e.g. OpenBSD + // Retrieve kernel info using `sysctl`, e.g. OpenBSD and NetBSD mib := []i32{sys.CTL_KERN, sys.KERN_OSTYPE} if !sys.sysctl(mib, &kernel_version_buf) { return @@ -61,7 +65,7 @@ init_os_version :: proc () { os_version.as_string = strings.to_string(b) } -@(init) +@(init, private) init_ram :: proc() { // Retrieve RAM info using `sysctl` mib := []i32{sys.CTL_HW, sys.HW_PHYSMEM64} @@ -69,4 +73,4 @@ init_ram :: proc() { if sys.sysctl(mib, &mem_size) { ram.total_ram = int(mem_size) } -} \ No newline at end of file +} diff --git a/core/sys/info/platform_darwin.odin b/core/sys/info/platform_darwin.odin index b95a48bd0..0cae0aa98 100644 --- a/core/sys/info/platform_darwin.odin +++ b/core/sys/info/platform_darwin.odin @@ -1,4 +1,3 @@ -// +build darwin package sysinfo import sys "core:sys/unix" @@ -76,6 +75,8 @@ init_os_version :: proc () { os_version.minor = rel.darwin.y os_version.patch = rel.darwin.z + macos_version = transmute(Version)rel.release.version + strings.write_string(&b, rel.os_name) if match == .Exact || match == .Nearest { strings.write_rune(&b, ' ') @@ -113,7 +114,7 @@ init_os_version :: proc () { os_version.as_string = strings.to_string(b) } -@(init) +@(init, private) init_ram :: proc() { // Retrieve RAM info using `sysctl` @@ -526,6 +527,7 @@ macos_release_map: map[string]Darwin_To_Release = { "23D60" = {{23, 3, 0}, "macOS", {"Sonoma", {14, 3, 1}}}, "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}}}, } @(private) diff --git a/core/sys/info/platform_freebsd.odin b/core/sys/info/platform_freebsd.odin index 26b4be7e9..b26fb7875 100644 --- a/core/sys/info/platform_freebsd.odin +++ b/core/sys/info/platform_freebsd.odin @@ -1,4 +1,3 @@ -// +build freebsd package sysinfo import sys "core:sys/unix" @@ -13,7 +12,7 @@ version_string_buf: [1024]u8 init_os_version :: proc () { os_version.platform = .FreeBSD - kernel_version_buf: [129]u8 + kernel_version_buf: [1024]u8 b := strings.builder_from_bytes(version_string_buf[:]) // Retrieve kernel info using `sysctl`, e.g. FreeBSD 13.1-RELEASE-p2 GENERIC @@ -68,7 +67,7 @@ init_os_version :: proc () { } } -@(init) +@(init, private) init_ram :: proc() { // Retrieve RAM info using `sysctl` mib := []i32{sys.CTL_HW, sys.HW_PHYSMEM} diff --git a/core/sys/info/platform_linux.odin b/core/sys/info/platform_linux.odin index 89b1204a7..45efc3329 100644 --- a/core/sys/info/platform_linux.odin +++ b/core/sys/info/platform_linux.odin @@ -1,11 +1,9 @@ -// +build linux package sysinfo import "base:intrinsics" -import "base:runtime" -import "core:strings" -import "core:strconv" +import "core:strconv" +import "core:strings" import "core:sys/linux" @(private) @@ -14,32 +12,37 @@ version_string_buf: [1024]u8 @(init, private) init_os_version :: proc () { os_version.platform = .Linux - // Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS` - fd, errno := linux.open("/etc/os-release", {.RDONLY}, {}) - assert(errno == .NONE, "Failed to read /etc/os-release") - defer { - cerrno := linux.close(fd) - assert(cerrno == .NONE, "Failed to close the file descriptor") - } - os_release_buf: [2048]u8 - n, read_errno := linux.read(fd, os_release_buf[:]) - assert(read_errno == .NONE, "Failed to read data from /etc/os-release") - release := string(os_release_buf[:n]) - // Search the line in the file until we find "PRETTY_NAME=" - NEEDLE :: "PRETTY_NAME=\"" - pretty_start := strings.index(release, NEEDLE) + b := strings.builder_from_bytes(version_string_buf[:]) - if pretty_start > 0 { - for r, i in release[pretty_start + len(NEEDLE):] { - if r == '"' { - strings.write_string(&b, release[pretty_start + len(NEEDLE):][:i]) - break - } else if r == '\r' || r == '\n' { - strings.write_string(&b, "Unknown Linux Distro") - break + + // Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS` + { + fd, errno := linux.open("/etc/os-release", {}) + assert(errno == .NONE, "Failed to read /etc/os-release") + defer { + cerrno := linux.close(fd) + assert(cerrno == .NONE, "Failed to close the file descriptor") + } + + os_release_buf: [2048]u8 + n, read_errno := linux.read(fd, os_release_buf[:]) + assert(read_errno == .NONE, "Failed to read data from /etc/os-release") + release := string(os_release_buf[:n]) + + // Search the line in the file until we find "PRETTY_NAME=" + NEEDLE :: "PRETTY_NAME=\"" + _, _, post := strings.partition(release, NEEDLE) + if len(post) > 0 { + end := strings.index_any(post, "\"\n") + if end > -1 && post[end] == '"' { + strings.write_string(&b, post[:end]) } } + if strings.builder_len(b) == 0 { + strings.write_string(&b, "Unknown Linux Distro") + } } + // Grab kernel info using `uname()` syscall, https://linux.die.net/man/2/uname uts: linux.UTS_Name uname_errno := linux.uname(&uts) @@ -48,31 +51,39 @@ init_os_version :: proc () { strings.write_string(&b, ", ") strings.write_string(&b, string(cstring(&uts.sysname[0]))) strings.write_rune(&b, ' ') - l := strings.builder_len(b) + + release_i := strings.builder_len(b) strings.write_string(&b, string(cstring(&uts.release[0]))) - // Parse kernel version, as substrings of the version info in `version_string_buf` - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - version_bits := strings.split_n(strings.to_string(b)[l:], "-", 2, context.temp_allocator) - if len(version_bits) > 1 { - os_version.version = version_bits[1] - } - // Parse major, minor, patch from release info - triplet := strings.split(version_bits[0], ".", context.temp_allocator) - if len(triplet) == 3 { - major, major_ok := strconv.parse_int(triplet[0]) - minor, minor_ok := strconv.parse_int(triplet[1]) - patch, patch_ok := strconv.parse_int(triplet[2]) - if major_ok && minor_ok && patch_ok { - os_version.major = major - os_version.minor = minor - os_version.patch = patch + release_str := string(b.buf[release_i:]) + + os_version.as_string = strings.to_string(b) + + // Parse the Linux version out of the release string + { + version_num, _, version_suffix := strings.partition(release_str, "-") + os_version.version = version_suffix + + i: int + for part in strings.split_iterator(&version_num, ".") { + defer i += 1 + + dst: ^int + switch i { + case 0: dst = &os_version.major + case 1: dst = &os_version.minor + case 2: dst = &os_version.patch + case: break + } + + num, ok := strconv.parse_int(part) + if !ok { break } + + dst^ = num } } - // Finish the string - os_version.as_string = strings.to_string(b) } -@(init) +@(init, private) init_ram :: proc() { // Retrieve RAM info using `sysinfo` sys_info: linux.Sys_Info @@ -84,4 +95,4 @@ init_ram :: proc() { total_swap = int(sys_info.totalswap) * int(sys_info.mem_unit), free_swap = int(sys_info.freeswap) * int(sys_info.mem_unit), } -} \ No newline at end of file +} diff --git a/core/sys/info/platform_windows.odin b/core/sys/info/platform_windows.odin index 250f938b1..4c00ddadf 100644 --- a/core/sys/info/platform_windows.odin +++ b/core/sys/info/platform_windows.odin @@ -1,4 +1,3 @@ -// +build windows package sysinfo import sys "core:sys/windows" @@ -259,7 +258,7 @@ init_os_version :: proc () { } } -@(init) +@(init, private) init_ram :: proc() { state: sys.MEMORYSTATUSEX diff --git a/core/sys/info/sysinfo.odin b/core/sys/info/sysinfo.odin index 69f9f1584..f0262f317 100644 --- a/core/sys/info/sysinfo.odin +++ b/core/sys/info/sysinfo.odin @@ -8,6 +8,9 @@ os_version: OS_Version ram: RAM gpus: []GPU +// Only on MacOS, contains the actual MacOS version, while the `os_version` contains the kernel version. +macos_version: Version + OS_Version_Platform :: enum { Unknown, Windows, @@ -19,12 +22,14 @@ OS_Version_Platform :: enum { NetBSD, } +Version :: struct { + major, minor, patch: int, +} + OS_Version :: struct { platform: OS_Version_Platform, - major: int, - minor: int, - patch: int, + using _: Version, build: [2]int, version: string, @@ -42,4 +47,4 @@ GPU :: struct { vendor_name: string, model_name: string, total_ram: int, -} \ No newline at end of file +} diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index 88c4f40c9..e10edf558 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -48,6 +48,7 @@ Errno :: enum i32 { ENOSYS = 38, ENOTEMPTY = 39, ELOOP = 40, + EUNKNOWN_41 = 41, ENOMSG = 42, EIDRM = 43, ECHRNG = 44, @@ -64,6 +65,7 @@ Errno :: enum i32 { ENOANO = 55, EBADRQC = 56, EBADSLT = 57, + EUNKNOWN_58 = 58, EBFONT = 59, ENOSTR = 60, ENODATA = 61, @@ -145,26 +147,70 @@ Errno :: enum i32 { } /* - Bits for Open_Flags + Bits for Open_Flags. + + RDONLY flag is not present, because it has the value of 0, i.e. it is the + default, unless WRONLY or RDWR is specified. */ -Open_Flags_Bits :: enum { - RDONLY = 0, - WRONLY = 1, - RDWR = 2, - CREAT = 6, - EXCL = 7, - NOCTTY = 8, - TRUNC = 9, - APPEND = 10, - NONBLOCK = 11, - DSYNC = 12, - ASYNC = 13, - DIRECT = 14, - DIRECTORY = 16, - NOFOLLOW = 17, - NOATIME = 18, - CLOEXEC = 19, - PATH = 21, +when ODIN_ARCH != .arm64 && ODIN_ARCH != .arm32 { + Open_Flags_Bits :: enum { + WRONLY = 0, + RDWR = 1, + CREAT = 6, + EXCL = 7, + NOCTTY = 8, + TRUNC = 9, + APPEND = 10, + NONBLOCK = 11, + DSYNC = 12, + ASYNC = 13, + DIRECT = 14, + LARGEFILE = 15, + DIRECTORY = 16, + NOFOLLOW = 17, + NOATIME = 18, + CLOEXEC = 19, + PATH = 21, + } + // https://github.com/torvalds/linux/blob/7367539ad4b0f8f9b396baf02110962333719a48/include/uapi/asm-generic/fcntl.h#L19 + #assert(1 << uint(Open_Flags_Bits.WRONLY) == 0o0000000_1) + #assert(1 << uint(Open_Flags_Bits.RDWR) == 0o0000000_2) + #assert(1 << uint(Open_Flags_Bits.CREAT) == 0o00000_100) + #assert(1 << uint(Open_Flags_Bits.EXCL) == 0o00000_200) + #assert(1 << uint(Open_Flags_Bits.NOCTTY) == 0o00000_400) + #assert(1 << uint(Open_Flags_Bits.TRUNC) == 0o0000_1000) + #assert(1 << uint(Open_Flags_Bits.APPEND) == 0o0000_2000) + #assert(1 << uint(Open_Flags_Bits.NONBLOCK) == 0o0000_4000) + #assert(1 << uint(Open_Flags_Bits.DSYNC) == 0o000_10000) + #assert(1 << uint(Open_Flags_Bits.ASYNC) == 0o000_20000) + #assert(1 << uint(Open_Flags_Bits.DIRECT) == 0o000_40000) + #assert(1 << uint(Open_Flags_Bits.LARGEFILE) == 0o00_100000) + #assert(1 << uint(Open_Flags_Bits.DIRECTORY) == 0o00_200000) + #assert(1 << uint(Open_Flags_Bits.NOFOLLOW) == 0o00_400000) + #assert(1 << uint(Open_Flags_Bits.NOATIME) == 0o0_1000000) + #assert(1 << uint(Open_Flags_Bits.CLOEXEC) == 0o0_2000000) + #assert(1 << uint(Open_Flags_Bits.PATH) == 0o_10000000) + +} else { + Open_Flags_Bits :: enum { + WRONLY = 0, + RDWR = 1, + CREAT = 6, + EXCL = 7, + NOCTTY = 8, + TRUNC = 9, + APPEND = 10, + NONBLOCK = 11, + DSYNC = 12, + ASYNC = 13, + DIRECTORY = 14, + NOFOLLOW = 15, + DIRECT = 16, + LARGEFILE = 17, + NOATIME = 18, + CLOEXEC = 19, + PATH = 21, + } } /* @@ -198,7 +244,7 @@ Mode_Bits :: enum { ISVTX = 9, // 0o0001000 ISGID = 10, // 0o0002000 ISUID = 11, // 0o0004000 - IFFIFO = 12, // 0o0010000 + IFIFO = 12, // 0o0010000 IFCHR = 13, // 0o0020000 IFDIR = 14, // 0o0040000 IFREG = 15, // 0o0100000 @@ -845,7 +891,7 @@ Wait_Option :: enum { WSTOPPED = 1, WEXITED = 2, WCONTINUED = 3, - WNOWAIT = 24, + WNOWAIT = 24, // // For processes created using clone __WNOTHREAD = 29, __WALL = 30, @@ -924,9 +970,22 @@ Sig_Stack_Flag :: enum i32 { AUTODISARM = 31, } +Sig_Action_Flag :: enum u32 { + NOCLDSTOP = 0, + NOCLDWAIT = 1, + SIGINFO = 2, + UNSUPPORTED = 10, + EXPOSE_TAGBITS = 11, + RESTORER = 26, + ONSTACK = 27, + RESTART = 28, + NODEFER = 30, + RESETHAND = 31, +} + /* Type of socket to create - - For TCP you want to use SOCK_STREAM + - For TCP you want to use SOCK_STREAM - For UDP you want to use SOCK_DGRAM Also see `Protocol` */ @@ -1405,16 +1464,16 @@ Futex_Flags_Bits :: enum { Kind of operation on futex, see FUTEX_WAKE_OP */ Futex_Arg_Op :: enum { - SET = 0, /* uaddr2 = oparg; */ - ADD = 1, /* uaddr2 += oparg; */ - OR = 2, /* uaddr2 |= oparg; */ - ANDN = 3, /* uaddr2 &= ~oparg; */ - XOR = 4, /* uaddr2 ^= oparg; */ - PO2_SET = 0, /* uaddr2 = 1< bool {return (S_IFFIFO == (m & S_IFMT))} +S_ISFIFO :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFIFO == (m & S_IFMT))} /* Check the Mode bits to see if the file is a character device. diff --git a/core/sys/linux/helpers.odin b/core/sys/linux/helpers.odin index 69c648bf1..75fdd586e 100644 --- a/core/sys/linux/helpers.odin +++ b/core/sys/linux/helpers.odin @@ -26,7 +26,7 @@ where @(private) syscall2 :: #force_inline proc "contextless" (nr: uintptr,p1: $T1, p2: $T2) -> int where - size_of(p1) <= size_of(uintptr) && + size_of(p1) <= size_of(uintptr), size_of(p2) <= size_of(uintptr) { return cast(int) intrinsics.syscall(nr, @@ -36,8 +36,8 @@ where @(private) syscall3 :: #force_inline proc "contextless" (nr: uintptr, p1: $T1, p2: $T2, p3: $T3) -> int where - size_of(p1) <= size_of(uintptr) && - size_of(p2) <= size_of(uintptr) && + size_of(p1) <= size_of(uintptr), + size_of(p2) <= size_of(uintptr), size_of(p3) <= size_of(uintptr) { return cast(int) intrinsics.syscall(nr, @@ -49,9 +49,9 @@ where @(private) syscall4 :: #force_inline proc "contextless" (nr: uintptr, p1: $T1, p2: $T2, p3: $T3, p4: $T4) -> int where - size_of(p1) <= size_of(uintptr) && - size_of(p2) <= size_of(uintptr) && - size_of(p3) <= size_of(uintptr) && + size_of(p1) <= size_of(uintptr), + size_of(p2) <= size_of(uintptr), + size_of(p3) <= size_of(uintptr), size_of(p4) <= size_of(uintptr) { return cast(int) intrinsics.syscall(nr, @@ -64,10 +64,10 @@ where @(private) syscall5 :: #force_inline proc "contextless" (nr: uintptr, p1: $T1, p2: $T2, p3: $T3, p4: $T4, p5: $T5) -> int where - size_of(p1) <= size_of(uintptr) && - size_of(p2) <= size_of(uintptr) && - size_of(p3) <= size_of(uintptr) && - size_of(p4) <= size_of(uintptr) && + size_of(p1) <= size_of(uintptr), + size_of(p2) <= size_of(uintptr), + size_of(p3) <= size_of(uintptr), + size_of(p4) <= size_of(uintptr), size_of(p5) <= size_of(uintptr) { return cast(int) intrinsics.syscall(nr, @@ -81,11 +81,11 @@ where @(private) syscall6 :: #force_inline proc "contextless" (nr: uintptr, p1: $T1, p2: $T2, p3: $T3, p4: $T4, p5: $T5, p6: $T6) -> int where - size_of(p1) <= size_of(uintptr) && - size_of(p2) <= size_of(uintptr) && - size_of(p3) <= size_of(uintptr) && - size_of(p4) <= size_of(uintptr) && - size_of(p5) <= size_of(uintptr) && + size_of(p1) <= size_of(uintptr), + size_of(p2) <= size_of(uintptr), + size_of(p3) <= size_of(uintptr), + size_of(p4) <= size_of(uintptr), + size_of(p5) <= size_of(uintptr), size_of(p6) <= size_of(uintptr) { return cast(int) intrinsics.syscall(nr, diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 887feb49d..03c34223c 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -69,7 +69,7 @@ close :: proc "contextless" (fd: Fd) -> (Errno) { stat :: proc "contextless" (filename: cstring, stat: ^Stat) -> (Errno) { when size_of(int) == 8 { when ODIN_ARCH == .arm64 { - ret := syscall(SYS_fstatat, AT_FDCWD, cast(rawptr) filename, stat) + ret := syscall(SYS_fstatat, AT_FDCWD, cast(rawptr) filename, stat, 0) return Errno(-ret) } else { ret := syscall(SYS_stat, cast(rawptr) filename, stat) @@ -200,10 +200,25 @@ brk :: proc "contextless" (addr: uintptr) -> (Errno) { return Errno(-ret) } +/* + Returns from signal handlers on some archs. +*/ +rt_sigreturn :: proc "c" () -> ! { + intrinsics.syscall(uintptr(SYS_rt_sigreturn)) + unreachable() +} + /* Alter an action taken by a process. */ -rt_sigaction :: proc "contextless" (sig: Signal, sigaction: ^Sig_Action, old_sigaction: ^Sig_Action) -> Errno { +rt_sigaction :: proc "contextless" (sig: Signal, sigaction: ^Sig_Action($T), old_sigaction: ^Sig_Action) -> Errno { + // NOTE(jason): It appears that the restorer is required for i386 and amd64 + when ODIN_ARCH == .i386 || ODIN_ARCH == .amd64 { + sigaction.flags += {.RESTORER} + } + if sigaction != nil && sigaction.restorer == nil && .RESTORER in sigaction.flags { + sigaction.restorer = rt_sigreturn + } ret := syscall(SYS_rt_sigaction, sig, sigaction, old_sigaction, size_of(Sig_Set)) return Errno(-ret) } @@ -487,6 +502,7 @@ connect :: proc "contextless" (sock: Fd, addr: ^$T) -> (Errno) where T == Sock_Addr_In || T == Sock_Addr_In6 || + T == Sock_Addr_Un || T == Sock_Addr_Any { ret := syscall(SYS_connect, sock, addr, size_of(T)) @@ -502,6 +518,7 @@ accept :: proc "contextless" (sock: Fd, addr: ^$T, sockflags: Socket_FD_Flags = where T == Sock_Addr_In || T == Sock_Addr_In6 || + T == Sock_Addr_Un || T == Sock_Addr_Any { addr_len: i32 = size_of(T) @@ -514,6 +531,7 @@ recvfrom :: proc "contextless" (sock: Fd, buf: []u8, flags: Socket_Msg, addr: ^$ where T == Sock_Addr_In || T == Sock_Addr_In6 || + T == Sock_Addr_Un || T == Sock_Addr_Any { addr_len: i32 = size_of(T) @@ -531,6 +549,7 @@ sendto :: proc "contextless" (sock: Fd, buf: []u8, flags: Socket_Msg, addr: ^$T) where T == Sock_Addr_In || T == Sock_Addr_In6 || + T == Sock_Addr_Un || T == Sock_Addr_Any { ret := syscall(SYS_sendto, sock, raw_data(buf), len(buf), transmute(i32) flags, addr, size_of(T)) @@ -590,6 +609,7 @@ bind :: proc "contextless" (sock: Fd, addr: ^$T) -> (Errno) where T == Sock_Addr_In || T == Sock_Addr_In6 || + T == Sock_Addr_Un || T == Sock_Addr_Any { ret := syscall(SYS_bind, sock, addr, size_of(T)) @@ -1114,7 +1134,7 @@ ftruncate :: proc "contextless" (fd: Fd, length: i64) -> (Errno) { ret := syscall(SYS_ftruncate64, fd, compat64_arg_pair(length)) return Errno(-ret) } else { - ret := syscall(SYS_truncate, fd, compat64_arg_pair(length)) + ret := syscall(SYS_ftruncate, fd, compat64_arg_pair(length)) return Errno(-ret) } } @@ -1222,7 +1242,7 @@ creat :: proc "contextless" (name: cstring, mode: Mode) -> (Fd, Errno) { */ link :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) { when ODIN_ARCH == .arm64 { - ret := syscall(SYS_linkat, AT_FDCWD, cast(rawptr) target, AT_FDCWD, cast(rawptr) linkpath) + ret := syscall(SYS_linkat, AT_FDCWD, cast(rawptr) target, AT_FDCWD, cast(rawptr) linkpath, 0) return Errno(-ret) } else { ret := syscall(SYS_link, cast(rawptr) target, cast(rawptr) linkpath) @@ -1252,7 +1272,7 @@ unlink :: proc "contextless" (name: cstring) -> (Errno) { */ symlink :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) { when ODIN_ARCH == .arm64 { - ret := syscall(SYS_symlinkat, AT_FDCWD, cast(rawptr) target, cast(rawptr) linkpath) + ret := syscall(SYS_symlinkat, cast(rawptr) target, AT_FDCWD, cast(rawptr) linkpath) return Errno(-ret) } else { ret := syscall(SYS_symlink, cast(rawptr) target, cast(rawptr) linkpath) @@ -1282,7 +1302,7 @@ readlink :: proc "contextless" (name: cstring, buf: []u8) -> (int, Errno) { */ chmod :: proc "contextless" (name: cstring, mode: Mode) -> (Errno) { when ODIN_ARCH == .arm64 { - ret := syscall(SYS_fchmodat, cast(rawptr) name, transmute(u32) mode, 0) + ret := syscall(SYS_fchmodat, AT_FDCWD, cast(rawptr) name, transmute(u32) mode) return Errno(-ret) } else { ret := syscall(SYS_chmod, cast(rawptr) name, transmute(u32) mode) @@ -1709,9 +1729,9 @@ getpgrp :: proc "contextless" () -> (Pid, Errno) { Create a session and set the process group ID. Available since Linux 2.0. */ -setsid :: proc "contextless" () -> (Errno) { +setsid :: proc "contextless" () -> (Pid, Errno) { ret := syscall(SYS_setsid) - return Errno(-ret) + return errno_unwrap(ret, Pid) } /* @@ -2217,8 +2237,7 @@ futex_wake :: proc "contextless" (futex: ^Futex, op: Futex_Wake_Type, flags: Fut Returns the total number of waiters that have been woken up plus the number of waiters requeued. */ futex_cmp_requeue :: proc "contextless" (futex: ^Futex, op: Futex_Cmp_Requeue_Type, flags: Futex_Flags, requeue_threshold: u32, - requeue_max: i32, requeue_futex: ^Futex, val: i32) -> (int, Errno) -{ + requeue_max: i32, requeue_futex: ^Futex, val: i32) -> (int, Errno) { futex_flags := cast(u32) op + transmute(u32) flags ret := syscall(SYS_futex, futex, futex_flags, requeue_threshold, requeue_max, requeue_futex, val) return errno_unwrap(ret, int) @@ -2229,8 +2248,7 @@ futex_cmp_requeue :: proc "contextless" (futex: ^Futex, op: Futex_Cmp_Requeue_Ty Returns the total number of waiters that have been woken up. */ futex_requeue :: proc "contextless" (futex: ^Futex, op: Futex_Requeue_Type, flags: Futex_Flags, requeue_threshold: u32, - requeue_max: i32, requeue_futex: ^Futex) -> (int, Errno) -{ + requeue_max: i32, requeue_futex: ^Futex) -> (int, Errno) { futex_flags := cast(u32) op + transmute(u32) flags ret := syscall(SYS_futex, futex, futex_flags, requeue_threshold, requeue_max, requeue_futex) return errno_unwrap(ret, int) @@ -2241,8 +2259,7 @@ futex_requeue :: proc "contextless" (futex: ^Futex, op: Futex_Requeue_Type, flag purpose is to allow implementing conditional values sync primitive, it seems like. */ futex_wake_op :: proc "contextless" (futex: ^Futex, op: Futex_Wake_Op_Type, flags: Futex_Flags, wakeup: i32, - dst_wakeup, dst: ^Futex, futex_op: u32) -> (int, Errno) -{ + dst_wakeup, dst: ^Futex, futex_op: u32) -> (int, Errno) { futex_flags := cast(u32) op + transmute(u32) flags ret := syscall(SYS_futex, futex, futex_flags, wakeup, dst_wakeup, dst, futex_op) return errno_unwrap(ret, int) @@ -2252,8 +2269,7 @@ futex_wake_op :: proc "contextless" (futex: ^Futex, op: Futex_Wake_Op_Type, flag Same as wait, but mask specifies bits that must be equal for the mutex to wake up. */ futex_wait_bitset :: proc "contextless" (futex: ^Futex, op: Futex_Wait_Bitset_Type, flags: Futex_Flags, val: u32, - timeout: ^Time_Spec, mask: u32) -> (int, Errno) -{ + timeout: ^Time_Spec, mask: u32) -> (int, Errno) { futex_flags := cast(u32) op + transmute(u32) flags ret := syscall(SYS_futex, futex, futex_flags, val, timeout, 0, mask) return errno_unwrap(ret, int) @@ -2262,8 +2278,7 @@ futex_wait_bitset :: proc "contextless" (futex: ^Futex, op: Futex_Wait_Bitset_Ty /* Wake up on bitset. */ -futex_wake_bitset :: proc "contextless" (futex: ^Futex, op: Futex_Wake_Bitset_Type, flags: Futex_Flags, n_wakeup: u32, mask: u32) -> (int, Errno) -{ +futex_wake_bitset :: proc "contextless" (futex: ^Futex, op: Futex_Wake_Bitset_Type, flags: Futex_Flags, n_wakeup: u32, mask: u32) -> (int, Errno) { futex_flags := cast(u32) op + transmute(u32) flags ret := syscall(SYS_futex, futex, futex_flags, n_wakeup, 0, 0, mask) return errno_unwrap(ret, int) @@ -2271,7 +2286,7 @@ futex_wake_bitset :: proc "contextless" (futex: ^Futex, op: Futex_Wake_Bitset_Ty // TODO(flysand): Priority inheritance (PI) futicees -futex :: proc { +futex :: proc{ futex_wait, futex_wake, futex_cmp_requeue, @@ -2310,7 +2325,7 @@ futex :: proc { */ epoll_create :: proc(size: i32 = 1) -> (Fd, Errno) { when ODIN_ARCH != .arm64 { - ret := syscall(SYS_epoll_create) + ret := syscall(SYS_epoll_create, i32(1)) return errno_unwrap(ret, Fd) } else { ret := syscall(SYS_epoll_create1, i32(0)) @@ -2467,8 +2482,8 @@ tgkill :: proc "contextless" (tgid, tid: Pid, sig: Signal) -> (Errno) { Wait on process, process group or pid file descriptor. Available since Linux 2.6.10. */ -waitid :: proc "contextless" (id_type: Id_Type, id: Id, sig_info: ^Sig_Info, options: Wait_Options) -> (Errno) { - ret := syscall(SYS_waitid, id_type, id, sig_info, transmute(i32) options) +waitid :: proc "contextless" (id_type: Id_Type, id: Id, sig_info: ^Sig_Info, options: Wait_Options, rusage: ^RUsage) -> (Errno) { + ret := syscall(SYS_waitid, id_type, id, sig_info, transmute(i32) options, rusage) return Errno(-ret) } @@ -2495,7 +2510,7 @@ waitid :: proc "contextless" (id_type: Id_Type, id: Id, sig_info: ^Sig_Info, opt Available since Linux 2.6.16. */ openat :: proc "contextless" (fd: Fd, name: cstring, flags: Open_Flags, mode: Mode = {}) -> (Fd, Errno) { - ret := syscall(SYS_openat, fd, AT_FDCWD, transmute(uintptr) name, transmute(u32) mode) + ret := syscall(SYS_openat, fd, transmute(uintptr) name, transmute(u32) flags, transmute(u32) mode) return errno_unwrap(ret, Fd) } @@ -2574,8 +2589,8 @@ linkat :: proc "contextless" (target_dirfd: Fd, oldpath: cstring, link_dirfd: Fd Create a symbolic link at specified dirfd. Available since Linux 2.6.16. */ -symlinkat :: proc "contextless" (dirfd: Fd, target: cstring, linkpath: cstring) -> (Errno) { - ret := syscall(SYS_symlinkat, dirfd, cast(rawptr) target, cast(rawptr) linkpath) +symlinkat :: proc "contextless" (target: cstring, dirfd: Fd, linkpath: cstring) -> (Errno) { + ret := syscall(SYS_symlinkat, cast(rawptr) target, dirfd, cast(rawptr) linkpath) return Errno(-ret) } @@ -2610,13 +2625,13 @@ faccessat :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode = F_OK) -> Wait for events on a file descriptor. Available since Linux 2.6.16. */ -ppoll :: proc "contextless" (fds: []Poll_Fd, timeout: ^Time_Spec, sigmask: ^Sig_Set) -> (Errno) { +ppoll :: proc "contextless" (fds: []Poll_Fd, timeout: ^Time_Spec, sigmask: ^Sig_Set) -> (i32, Errno) { when size_of(int) == 8 { ret := syscall(SYS_ppoll, raw_data(fds), len(fds), timeout, sigmask, size_of(Sig_Set)) - return Errno(-ret) + return errno_unwrap(ret, i32) } else { ret := syscall(SYS_ppoll_time64, raw_data(fds), len(fds), timeout, sigmask, size_of(Sig_Set)) - return Errno(-ret) + return errno_unwrap(ret, i32) } } @@ -2799,7 +2814,7 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er Execute program relative to a directory file descriptor. Available since Linux 3.19. */ -execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: Execveat_Flags) -> (Errno) { +execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: FD_Flags = {}) -> (Errno) { ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, transmute(i32) flags) return Errno(-ret) } diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index e7ecfc609..81ec0bedd 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -18,7 +18,7 @@ Gid :: distinct u32 /* Type for Process IDs, Thread IDs, Thread group ID. */ -Pid :: distinct int +Pid :: distinct i32 /* Type for any of: pid, pidfd, pgid. @@ -89,11 +89,11 @@ FD_Flags :: bit_set[FD_Flags_Bits; i32] Represents file's permission and status bits **Example:** When you're passing a value of this type the recommended usage is: - + ``` linux.Mode{.S_IXOTH, .S_IROTH} | linux.S_IRWXU | linux.S_IRWXG ``` - + This would generate a mode that has full permissions for the file's owner and group, and only "read" and "execute" bits for others. @@ -151,9 +151,9 @@ when ODIN_ARCH == .amd64 { size: i64, blksize: uint, blocks: u64, - atim: Time_Spec, - mtim: Time_Spec, - ctim: Time_Spec, + atime: Time_Spec, + mtime: Time_Spec, + ctime: Time_Spec, ino: Inode, } } @@ -495,16 +495,15 @@ Pid_FD_Flags :: bit_set[Pid_FD_Flags_Bits; i32] // 1. Odin's bitfields start from 0, whereas signals start from 1 // 2. It's unclear how bitfields act in terms of ABI (are they an array of ints or an array of longs?). // it makes a difference because ARM is big endian. -@private _SIGSET_NWORDS :: (1024 / (8 * size_of(uint))) +@private _SIGSET_NWORDS :: (8 / size_of(uint)) Sig_Set :: [_SIGSET_NWORDS]uint @private SI_MAX_SIZE :: 128 -@private SI_ARCH_PREAMBLE :: 3 * size_of(i32) -@private SI_PAD_SIZE :: (SI_MAX_SIZE - SI_ARCH_PREAMBLE) / size_of(i32) -@private SI_TIMER_PAD_SIZE :: size_of(Uid) - size_of(i32) +@private SI_ARCH_PREAMBLE :: 4 * size_of(i32) +@private SI_PAD_SIZE :: SI_MAX_SIZE - SI_ARCH_PREAMBLE Sig_Handler_Fn :: #type proc "c" (sig: Signal) -Sig_Restore_Fn :: #type proc "c" () +Sig_Restore_Fn :: #type proc "c" () -> ! Sig_Info :: struct #packed { signo: Signal, @@ -518,8 +517,9 @@ Sig_Info :: struct #packed { uid: Uid, /* sender's uid */ }, using _timer: struct { - timerid: i32, /* timer id */ + timerid: i32, /* timer id */ overrun: i32, /* overrun count */ + value: Sig_Val, /* timer value */ }, /* POSIX.1b signals */ using _rt: struct { @@ -528,8 +528,8 @@ Sig_Info :: struct #packed { }, /* SIGCHLD */ using _sigchld: struct { - _pid1: Pid, /* which child */ - _uid1: Uid, /* sender's uid */ + _pid1: Pid, /* which child */ + _uid1: Uid, /* sender's uid */ status: i32, /* exit code */ utime: uint, stime: uint, //clock_t @@ -537,7 +537,24 @@ Sig_Info :: struct #packed { /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */ using _sigfault: struct { addr: rawptr, /* faulting insn/memory ref. */ - addr_lsb: i16, /* LSB of the reported address */ + using _: struct #raw_union { + trapno: i32, /* Trap number that caused signal */ + addr_lsb: i16, /* LSB of the reported address */ + using _addr_bnd: struct { + _pad2: u64, + lower: rawptr, /* lower bound during fault */ + upper: rawptr, /* upper bound during fault */ + }, + using _addr_pkey: struct { + _pad3: u64, + pkey: u32, /* protection key on PTE that faulted */ + }, + using _perf: struct { + perf_data: u64, + perf_type: u32, + perf_flags: u32, + }, + }, }, /* SIGPOLL */ using _sigpoll: struct { @@ -547,12 +564,43 @@ Sig_Info :: struct #packed { /* SIGSYS */ using _sigsys: struct { call_addr: rawptr, /* calling user insn */ - syscall: i32, /* triggering system call number */ - arch: u32, /* AUDIT_ARCH_* of syscall */ + syscall: i32, /* triggering system call number */ + arch: u32, /* AUDIT_ARCH_* of syscall */ }, }, } +#assert(size_of(Sig_Info) == 128) +when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + #assert(offset_of(Sig_Info, signo) == 0x00) + #assert(offset_of(Sig_Info, errno) == 0x04) + #assert(offset_of(Sig_Info, code) == 0x08) + #assert(offset_of(Sig_Info, pid) == 0x10) + #assert(offset_of(Sig_Info, uid) == 0x14) + #assert(offset_of(Sig_Info, timerid) == 0x10) + #assert(offset_of(Sig_Info, overrun) == 0x14) + #assert(offset_of(Sig_Info, value) == 0x18) + #assert(offset_of(Sig_Info, status) == 0x18) + #assert(offset_of(Sig_Info, utime) == 0x20) + #assert(offset_of(Sig_Info, stime) == 0x28) + #assert(offset_of(Sig_Info, addr) == 0x10) + #assert(offset_of(Sig_Info, addr_lsb) == 0x18) + #assert(offset_of(Sig_Info, trapno) == 0x18) + #assert(offset_of(Sig_Info, lower) == 0x20) + #assert(offset_of(Sig_Info, upper) == 0x28) + #assert(offset_of(Sig_Info, pkey) == 0x20) + #assert(offset_of(Sig_Info, perf_data) == 0x18) + #assert(offset_of(Sig_Info, perf_type) == 0x20) + #assert(offset_of(Sig_Info, perf_flags) == 0x24) + #assert(offset_of(Sig_Info, band) == 0x10) + #assert(offset_of(Sig_Info, fd) == 0x18) + #assert(offset_of(Sig_Info, call_addr) == 0x10) + #assert(offset_of(Sig_Info, syscall) == 0x18) + #assert(offset_of(Sig_Info, arch) == 0x1C) +} else { + // TODO +} + SIGEV_MAX_SIZE :: 64 SIGEV_PAD_SIZE :: ((SIGEV_MAX_SIZE-size_of(i32)*2+size_of(Sig_Val))/size_of(i32)) @@ -583,12 +631,20 @@ Sig_Stack :: struct { size: uintptr, } +Sig_Action_Special :: enum uint { + SIG_DFL = 0, + SIG_IGN = 1, + SIG_ERR = ~uint(0), +} + +Sig_Action_Flags :: bit_set[Sig_Action_Flag; uint] Sig_Action :: struct($T: typeid) { using _u: struct #raw_union { handler: Sig_Handler_Fn, sigaction: #type proc "c" (sig: Signal, si: ^Sig_Info, ctx: ^T), + special: Sig_Action_Special, }, - flags: uint, + flags: Sig_Action_Flags, restorer: Sig_Restore_Fn, mask: Sig_Set, } @@ -631,6 +687,14 @@ Sock_Addr_In6 :: struct #packed { sin6_scope_id: u32, } +/* + Struct representing Unix Domain Socket address +*/ +Sock_Addr_Un :: struct #packed { + sun_family: Address_Family, + sun_path: [108]u8, +} + /* Struct representing an arbitrary socket address. */ @@ -641,6 +705,7 @@ Sock_Addr_Any :: struct #raw_union { }, using ipv4: Sock_Addr_In, using ipv6: Sock_Addr_In6, + using uds: Sock_Addr_Un, } /* @@ -724,7 +789,7 @@ RLimit :: struct { /* Structure representing how much of each resource got used. -*/ +*/ RUsage :: struct { utime: Time_Val, stime: Time_Val, @@ -804,7 +869,7 @@ when size_of(int) == 8 || ODIN_ARCH == .i386 { cpid: Pid, lpid: Pid, nattach: uint, - _: [2]uint, + _: [2]uint, } } diff --git a/core/sys/linux/wrappers.odin b/core/sys/linux/wrappers.odin index 13073315d..7a30c3bde 100644 --- a/core/sys/linux/wrappers.odin +++ b/core/sys/linux/wrappers.odin @@ -48,7 +48,7 @@ WCOREDUMP :: #force_inline proc "contextless" (s: u32) -> bool { return 1 << ((cast(uint)(sig) - 1) % (8*size_of(uint))) } @private _sigword :: proc "contextless" (sig: Signal) -> (uint) { - return (cast(uint)sig - 1) / (8*size_of(uint)) + return (cast(uint)sig - 1) / (8*size_of(uint)) } // TODO: sigaddset etc @@ -85,13 +85,13 @@ dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent, /// Obtain the name of dirent as a string /// The lifetime of the string is bound to the lifetime of the provided dirent structure dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check { - str := transmute([^]u8) &dirent.name + str := ([^]u8)(&dirent.name) // Note(flysand): The string size calculated above applies only to the ideal case // we subtract 1 byte from the string size, because a null terminator is guaranteed // to be present. But! That said, the dirents are aligned to 8 bytes and the padding // between the null terminator and the start of the next struct may be not initialized // which means we also have to scan these garbage bytes. - str_size := (cast(int) dirent.reclen) - 1 - cast(int) offset_of(Dirent, name) + str_size := int(dirent.reclen) - 1 - cast(int)offset_of(Dirent, name) // This skips *only* over the garbage, since if we're not garbage we're at nul terminator, // which skips this loop for str[str_size] != 0 { @@ -115,7 +115,6 @@ futex_op :: proc "contextless" (arg_op: Futex_Arg_Op, cmp_op: Futex_Cmp_Op, op_a /// Helper function for constructing the config for caches perf_cache_config :: #force_inline proc "contextless" (id: Perf_Hardware_Cache_Id, op: Perf_Hardware_Cache_Op_Id, - res: Perf_Hardware_Cache_Result_Id) -> u64 -{ + res: Perf_Hardware_Cache_Result_Id) -> u64 { return u64(id) | (u64(op) << 8) | (u64(res) << 16) } \ No newline at end of file diff --git a/core/sys/unix/pthread_freebsd.odin b/core/sys/unix/pthread_freebsd.odin index 3417d3943..5f4dac289 100644 --- a/core/sys/unix/pthread_freebsd.odin +++ b/core/sys/unix/pthread_freebsd.odin @@ -95,7 +95,7 @@ sem_t :: struct { PTHREAD_CANCEL_ENABLE :: 0 PTHREAD_CANCEL_DISABLE :: 1 PTHREAD_CANCEL_DEFERRED :: 0 -PTHREAD_CANCEL_ASYNCHRONOUS :: 1 +PTHREAD_CANCEL_ASYNCHRONOUS :: 2 foreign import "system:pthread" @@ -119,4 +119,4 @@ foreign pthread { pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- pthread_cancel :: proc (thread: pthread_t) -> c.int --- -} \ No newline at end of file +} diff --git a/core/sys/unix/pthread_netbsd.odin b/core/sys/unix/pthread_netbsd.odin new file mode 100644 index 000000000..afbbc321c --- /dev/null +++ b/core/sys/unix/pthread_netbsd.odin @@ -0,0 +1,102 @@ +package unix + +import "core:c" + +pthread_t :: distinct u64 + +SEM_T_SIZE :: 8 + +PTHREAD_CONDATTR_T_SIZE :: 16 +PTHREAD_MUTEXATTR_T_SIZE :: 16 +PTHREAD_RWLOCKATTR_T_SIZE :: 16 +PTHREAD_BARRIERATTR_T_SIZE :: 16 + +PTHREAD_COND_T_SIZE :: 40 +PTHREAD_MUTEX_T_SIZE :: 48 +PTHREAD_RWLOCK_T_SIZE :: 64 +PTHREAD_BARRIER_T_SIZE :: 48 +PTHREAD_ATTR_T_SIZE :: 16 + +pthread_cond_t :: struct #align(8) { + _: [PTHREAD_COND_T_SIZE] c.char, +} + +pthread_mutex_t :: struct #align(8) { + _: [PTHREAD_MUTEX_T_SIZE] c.char, +} + +pthread_rwlock_t :: struct #align(8) { + _: [PTHREAD_RWLOCK_T_SIZE] c.char, +} + +pthread_barrier_t :: struct #align(8) { + _: [PTHREAD_BARRIER_T_SIZE] c.char, +} + +pthread_attr_t :: struct #align(8) { + _: [PTHREAD_ATTR_T_SIZE] c.char, +} + +pthread_condattr_t :: struct #align(8) { + _: [PTHREAD_CONDATTR_T_SIZE] c.char, +} + +pthread_mutexattr_t :: struct #align(8) { + _: [PTHREAD_MUTEXATTR_T_SIZE] c.char, +} + +pthread_rwlockattr_t :: struct #align(8) { + _: [PTHREAD_RWLOCKATTR_T_SIZE] c.char, +} + +pthread_barrierattr_t :: struct #align(8) { + _: [PTHREAD_BARRIERATTR_T_SIZE] c.char, +} + +PTHREAD_MUTEX_NORMAL :: 0 +PTHREAD_MUTEX_ERRORCHECK :: 1 +PTHREAD_MUTEX_RECURSIVE :: 2 + +PTHREAD_CREATE_JOINABLE :: 0 +PTHREAD_CREATE_DETACHED :: 1 +PTHREAD_INHERIT_SCHED :: 0 +PTHREAD_EXPLICIT_SCHED :: 1 +PTHREAD_PROCESS_PRIVATE :: 0 +PTHREAD_PROCESS_SHARED :: 1 + +SCHED_NONE :: -1 +SCHED_OTHER :: 0 +SCHED_FIFO :: 1 +SCHED_RR :: 3 + +sched_param :: struct { + sched_priority: c.int, +} + +sem_t :: struct #align(16) { + _: [SEM_T_SIZE] c.char, +} + +PTHREAD_CANCEL_ENABLE :: 0 +PTHREAD_CANCEL_DISABLE :: 1 +PTHREAD_CANCEL_DEFERRED :: 0 +PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + +foreign import "system:pthread" + +@(default_calling_convention="c") +foreign pthread { + sem_open :: proc(name: cstring, flags: c.int) -> ^sem_t --- + + sem_init :: proc(sem: ^sem_t, pshared: c.int, initial_value: c.uint) -> c.int --- + sem_destroy :: proc(sem: ^sem_t) -> c.int --- + sem_post :: proc(sem: ^sem_t) -> c.int --- + sem_wait :: proc(sem: ^sem_t) -> c.int --- + sem_trywait :: proc(sem: ^sem_t) -> c.int --- + + pthread_yield :: proc() --- + + pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- + pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- + pthread_cancel :: proc (thread: pthread_t) -> c.int --- +} diff --git a/core/sys/unix/pthread_openbsd.odin b/core/sys/unix/pthread_openbsd.odin index 7ae82e662..855e7d99c 100644 --- a/core/sys/unix/pthread_openbsd.odin +++ b/core/sys/unix/pthread_openbsd.odin @@ -49,7 +49,7 @@ sem_t :: distinct rawptr PTHREAD_CANCEL_ENABLE :: 0 PTHREAD_CANCEL_DISABLE :: 1 PTHREAD_CANCEL_DEFERRED :: 0 -PTHREAD_CANCEL_ASYNCHRONOUS :: 1 +PTHREAD_CANCEL_ASYNCHRONOUS :: 2 foreign import libc "system:c" @@ -71,4 +71,4 @@ foreign libc { pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- pthread_cancel :: proc (thread: pthread_t) -> c.int --- -} \ No newline at end of file +} diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin index 4fe3c8dfa..c876a214a 100644 --- a/core/sys/unix/pthread_unix.odin +++ b/core/sys/unix/pthread_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku package unix foreign import "system:pthread" @@ -116,4 +116,5 @@ foreign pthread { pthread_mutexattr_setpshared :: proc(attrs: ^pthread_mutexattr_t, value: c.int) -> c.int --- pthread_mutexattr_getpshared :: proc(attrs: ^pthread_mutexattr_t, result: ^c.int) -> c.int --- + pthread_testcancel :: proc () --- } diff --git a/core/sys/unix/syscalls_netbsd.odin b/core/sys/unix/syscalls_netbsd.odin new file mode 100644 index 000000000..e92dc6d92 --- /dev/null +++ b/core/sys/unix/syscalls_netbsd.odin @@ -0,0 +1,3 @@ +package unix + +SYS___futex : uintptr : 166 diff --git a/core/sys/unix/sysctl_darwin.odin b/core/sys/unix/sysctl_darwin.odin index 76c72f478..14d3c113a 100644 --- a/core/sys/unix/sysctl_darwin.odin +++ b/core/sys/unix/sysctl_darwin.odin @@ -1,22 +1,62 @@ //+build darwin package unix -import "core:sys/darwin" import "base:intrinsics" +import "core:sys/darwin" + _ :: darwin -sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) { - mib := mib - result_size := i64(size_of(T)) +sysctl :: proc "contextless" (mib: []i32, val: ^$T) -> (ok: bool) { + result_size := uint(size_of(T)) + when ODIN_NO_CRT { + res := darwin.syscall_sysctl( + raw_data(mib), len(mib), + val, &result_size, + nil, 0, + ) + return res == 0 + } else { + foreign { + @(link_name="sysctl") _sysctl :: proc( + name: [^]i32, namelen: u32, + oldp: rawptr, oldlenp: ^uint, + newp: rawptr, newlen: uint, + ) -> i32 --- + } + res := _sysctl( + raw_data(mib), u32(len(mib)), + val, &result_size, + nil, 0, + ) + return res == 0 + } +} - res := intrinsics.syscall( - darwin.unix_offset_syscall(.sysctl), - uintptr(raw_data(mib)), uintptr(len(mib)), - uintptr(val), uintptr(&result_size), - uintptr(0), uintptr(0), - ) - return res == 0 +sysctlbyname :: proc "contextless" (name: cstring, val: ^$T) -> (ok: bool) { + result_size := uint(size_of(T)) + when ODIN_NO_CRT { + res := darwin.syscall_sysctlbyname( + string(name), + val, &result_size, + nil, 0, + ) + return res == 0 + } else { + foreign { + @(link_name="sysctlbyname") _sysctlbyname :: proc( + name: cstring, + oldp: rawptr, oldlenp: ^uint, + newp: rawptr, newlen: uint, + ) -> i32 --- + } + res := _sysctlbyname( + name, + val, &result_size, + nil, 0, + ) + return res == 0 + } } // See sysctl.h for darwin for details diff --git a/core/sys/unix/sysctl_freebsd.odin b/core/sys/unix/sysctl_freebsd.odin index d1acbc2a1..35c5db02c 100644 --- a/core/sys/unix/sysctl_freebsd.odin +++ b/core/sys/unix/sysctl_freebsd.odin @@ -5,14 +5,15 @@ import "base:intrinsics" sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) { mib := mib - result_size := i64(size_of(T)) + result_size := u64(size_of(T)) - res := intrinsics.syscall(SYS_sysctl, + res: uintptr + res, ok = intrinsics.syscall_bsd(SYS_sysctl, uintptr(raw_data(mib)), uintptr(len(mib)), uintptr(val), uintptr(&result_size), uintptr(0), uintptr(0), ) - return res == 0 + return } // See /usr/include/sys/sysctl.h for details diff --git a/core/sys/unix/sysctl_netbsd.odin b/core/sys/unix/sysctl_netbsd.odin new file mode 100644 index 000000000..ad89b9ad4 --- /dev/null +++ b/core/sys/unix/sysctl_netbsd.odin @@ -0,0 +1,44 @@ +package unix + +import "core:c" +foreign import libc "system:c" + +@(default_calling_convention="c") +foreign libc { + @(link_name="sysctl") _unix_sysctl :: proc(name: [^]i32, namelen: u32, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> i32 --- +} + +sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) { + mib := mib + result_size := c.size_t(size_of(T)) + res := _unix_sysctl(raw_data(mib), u32(len(mib)), val, &result_size, nil, 0) + return res == 0 +} + +// See /usr/include/sys/sysctl.h for details +CTL_KERN :: 1 + KERN_OSTYPE :: 1 + KERN_OSRELEASE :: 2 + KERN_OSREV :: 3 + KERN_VERSION :: 4 +CTL_VM :: 2 +CTL_FS :: 3 +CTL_NET :: 4 +CTL_DEBUG :: 5 +CTL_HW :: 6 + HW_MACHINE :: 1 + HW_MODEL :: 2 + HW_NCPU :: 3 + HW_BYTEORDER :: 4 + HW_PHYSMEM :: 5 + HW_USERMEM :: 6 + HW_PAGESIZE :: 7 + HW_DISKNAMES :: 8 + HW_IOSTATS :: 9 + HW_MACHINE_ARCH :: 10 + HW_ALIGNBYTES :: 11 + HW_CNMAGIC :: 12 + HW_PHYSMEM64 :: 13 + HW_USERMEM64 :: 14 + HW_IOSTATNAMES :: 15 + HW_NCPUONLINE :: 16 diff --git a/core/sys/unix/time_unix.odin b/core/sys/unix/time_unix.odin index 088dc378b..442202d36 100644 --- a/core/sys/unix/time_unix.odin +++ b/core/sys/unix/time_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku package unix when ODIN_OS == .Darwin { @@ -9,11 +9,20 @@ when ODIN_OS == .Darwin { import "core:c" -@(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 --- +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 { @@ -65,7 +74,6 @@ seconds_since_boot :: proc "c" () -> f64 { 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} diff --git a/core/sys/wasm/wasi/wasi_api.odin b/core/sys/wasm/wasi/wasi_api.odin index e9ceb4667..22abd8dc4 100644 --- a/core/sys/wasm/wasi/wasi_api.odin +++ b/core/sys/wasm/wasi/wasi_api.odin @@ -962,7 +962,7 @@ prestat_dir_t :: struct { } prestat_t :: struct { - tag: u8, + tag: preopentype_t, using u: struct { dir: prestat_dir_t, }, @@ -1158,7 +1158,7 @@ foreign wasi { /** * A buffer into which to write the preopened directory name. */ - path: string, + path: []byte, ) -> errno_t --- /** * Create a directory. diff --git a/core/sys/windows/advapi32.odin b/core/sys/windows/advapi32.odin index 163bf2a5e..4a7074506 100644 --- a/core/sys/windows/advapi32.odin +++ b/core/sys/windows/advapi32.odin @@ -18,6 +18,14 @@ foreign advapi32 { OpenAsSelf: BOOL, TokenHandle: ^HANDLE) -> BOOL --- + GetTokenInformation :: proc ( + TokenHandle: HANDLE, + TokenInformationClass: TOKEN_INFORMATION_CLASS, + TokenInformation: LPVOID, + TokenInformationLength: DWORD, + ReturnLength: PDWORD, + ) -> BOOL --- + CryptAcquireContextW :: proc(hProv: ^HCRYPTPROV, szContainer, szProvider: wstring, dwProvType, dwFlags: DWORD) -> DWORD --- CryptGenRandom :: proc(hProv: HCRYPTPROV, dwLen: DWORD, buf: LPVOID) -> DWORD --- CryptReleaseContext :: proc(hProv: HCRYPTPROV, dwFlags: DWORD) -> DWORD --- @@ -44,7 +52,17 @@ foreign advapi32 { cbSid: ^DWORD, ReferencedDomainName: wstring, cchReferencedDomainName: ^DWORD, - peUse: ^SID_TYPE, + peUse: PSID_NAME_USE, + ) -> BOOL --- + + LookupAccountSidW :: proc ( + lpSystemName: LPCWSTR, + Sid: PSID, + Name: LPWSTR, + cchName: LPDWORD, + ReferencedDomainName: LPWSTR, + cchReferencedDomainName: LPDWORD, + peUse: PSID_NAME_USE, ) -> BOOL --- CreateProcessWithLogonW :: proc( @@ -164,3 +182,156 @@ foreign advapi32 { AccessStatus: LPBOOL, ) -> BOOL --- } + +PTOKEN_INFORMATION_CLASS :: ^TOKEN_INFORMATION_CLASS +TOKEN_INFORMATION_CLASS :: enum i32 { + TokenUser = 1, + TokenGroups, + TokenPrivileges, + TokenOwner, + TokenPrimaryGroup, + TokenDefaultDacl, + TokenSource, + TokenType, + TokenImpersonationLevel, + TokenStatistics, + TokenRestrictedSids, + TokenSessionId, + TokenGroupsAndPrivileges, + TokenSessionReference, + TokenSandBoxInert, + TokenAuditPolicy, + TokenOrigin, + TokenElevationType, + TokenLinkedToken, + TokenElevation, + TokenHasRestrictions, + TokenAccessInformation, + TokenVirtualizationAllowed, + TokenVirtualizationEnabled, + TokenIntegrityLevel, + TokenUIAccess, + TokenMandatoryPolicy, + TokenLogonSid, + TokenIsAppContainer, + TokenCapabilities, + TokenAppContainerSid, + TokenAppContainerNumber, + TokenUserClaimAttributes, + TokenDeviceClaimAttributes, + TokenRestrictedUserClaimAttributes, + TokenRestrictedDeviceClaimAttributes, + TokenDeviceGroups, + TokenRestrictedDeviceGroups, + TokenSecurityAttributes, + TokenIsRestricted, + TokenProcessTrustLevel, + TokenPrivateNameSpace, + TokenSingletonAttributes, + TokenBnoIsolation, + TokenChildProcessFlags, + TokenIsLessPrivilegedAppContainer, + TokenIsSandboxed, + TokenIsAppSilo, + TokenLoggingInformation, + MaxTokenInfoClass, +} + +PSID_NAME_USE :: ^SID_NAME_USE +SID_NAME_USE :: enum i32 { + SidTypeUser = 1, + SidTypeGroup, + SidTypeDomain, + SidTypeAlias, + SidTypeWellKnownGroup, + SidTypeDeletedAccount, + SidTypeInvalid, + SidTypeUnknown, + SidTypeComputer, + SidTypeLabel, + SidTypeLogonSession, +} + +PTOKEN_USER :: ^TOKEN_USER +TOKEN_USER :: struct { + User: SID_AND_ATTRIBUTES, +} + +PSID_AND_ATTRIBUTES :: ^SID_AND_ATTRIBUTES +SID_AND_ATTRIBUTES :: struct { + Sid: rawptr, + Attributes: ULONG, +} + +PTOKEN_TYPE :: ^TOKEN_TYPE +TOKEN_TYPE :: enum { + TokenPrimary = 1, + TokenImpersonation = 2, +} + +PTOKEN_STATISTICS :: ^TOKEN_STATISTICS +TOKEN_STATISTICS :: struct { + TokenId: LUID, + AuthenticationId: LUID, + ExpirationTime: LARGE_INTEGER, + TokenType: TOKEN_TYPE, + ImpersonationLevel: SECURITY_IMPERSONATION_LEVEL, + DynamicCharged: DWORD, + DynamicAvailable: DWORD, + GroupCount: DWORD, + PrivilegeCount: DWORD, + ModifiedId: LUID, +} + + +TOKEN_SOURCE_LENGTH :: 8 +PTOKEN_SOURCE :: ^TOKEN_SOURCE +TOKEN_SOURCE :: struct { + SourceName: [TOKEN_SOURCE_LENGTH]CHAR, + SourceIdentifier: LUID, +} + + +PTOKEN_PRIVILEGES :: ^TOKEN_PRIVILEGES +TOKEN_PRIVILEGES :: struct { + PrivilegeCount: DWORD, + Privileges: [0]LUID_AND_ATTRIBUTES, +} + +PTOKEN_PRIMARY_GROUP :: ^TOKEN_PRIMARY_GROUP +TOKEN_PRIMARY_GROUP :: struct { + PrimaryGroup: PSID, +} + +PTOKEN_OWNER :: ^TOKEN_OWNER +TOKEN_OWNER :: struct { + Owner: PSID, +} + +PTOKEN_GROUPS_AND_PRIVILEGES :: ^TOKEN_GROUPS_AND_PRIVILEGES +TOKEN_GROUPS_AND_PRIVILEGES :: struct { + SidCount: DWORD, + SidLength: DWORD, + Sids: PSID_AND_ATTRIBUTES, + RestrictedSidCount: DWORD, + RestrictedSidLength: DWORD, + RestrictedSids: PSID_AND_ATTRIBUTES, + PrivilegeCount: DWORD, + PrivilegeLength: DWORD, + Privileges: PLUID_AND_ATTRIBUTES, + AuthenticationId: LUID, +} + +PTOKEN_DEFAULT_DACL :: ^TOKEN_DEFAULT_DACL +TOKEN_DEFAULT_DACL :: struct { + DefaultDacl: PACL, +} + +PACL :: ^ACL +ACL :: struct { + AclRevision: BYTE, + Sbz1: BYTE, + AclSize: WORD, + AceCount: WORD, + Sbz2: WORD, +} diff --git a/core/sys/windows/dbghelp.odin b/core/sys/windows/dbghelp.odin index c2e506748..cb5458248 100644 --- a/core/sys/windows/dbghelp.odin +++ b/core/sys/windows/dbghelp.odin @@ -228,6 +228,38 @@ MINIDUMP_TYPE :: enum u32 { ValidTypeFlags = 0x01ffffff, } + +SYMBOL_INFOW :: struct { + SizeOfStruct: ULONG, + TypeIndex: ULONG, + Reserved: [2]ULONG64, + Index: ULONG, + Size: ULONG, + ModBase: ULONG64, + Flags: ULONG, + Value: ULONG64, + Address: ULONG64, + Register: ULONG, + Scope: ULONG, + Tag: ULONG, + NameLen: ULONG, + MaxNameLen: ULONG, + Name: [1]WCHAR, +} + +IMAGEHLP_LINE64 :: struct { + SizeOfStruct: DWORD, + Key: PVOID, + LineNumber: DWORD, + FileName: PWSTR, + Address: DWORD64, +} + +PSYMBOL_INFOW :: ^SYMBOL_INFOW +PIMAGEHLP_LINEW64 :: ^IMAGEHLP_LINE64 + +SYMOPT_LOAD_LINES :: 0x00000010 + @(default_calling_convention = "system") foreign Dbghelp { MiniDumpWriteDump :: proc( @@ -247,4 +279,10 @@ foreign Dbghelp { StreamPointer: ^PVOID, StreamSize: ^ULONG, ) -> BOOL --- + + SymInitialize :: proc(hProcess: HANDLE, UserSearchPath: PCSTR, fInvadeProcess: BOOL) -> BOOL --- + SymCleanup :: proc(hProcess: HANDLE) -> BOOL --- + SymSetOptions :: proc(SymOptions: DWORD) -> DWORD --- + SymFromAddrW :: proc(hProcess: HANDLE, Address: DWORD64, Displacement: PDWORD64, Symbol: PSYMBOL_INFOW) -> BOOL --- + SymGetLineFromAddrW64 :: proc(hProcess: HANDLE, dwAddr: DWORD64, pdwDisplacement: PDWORD, Line: PIMAGEHLP_LINEW64) -> BOOL --- } diff --git a/core/sys/windows/hidpi.odin b/core/sys/windows/hidpi.odin index f862971f4..bea03694e 100644 --- a/core/sys/windows/hidpi.odin +++ b/core/sys/windows/hidpi.odin @@ -2,8 +2,20 @@ package sys_windows import "core:c" -USAGE :: distinct USHORT -PUSAGE :: ^USAGE +HIDD_CONFIGURATION :: struct { + cookie: PVOID, + size: ULONG, + RingBufferSize: ULONG, +} +PHIDD_CONFIGURATION :: ^HIDD_CONFIGURATION + +HIDD_ATTRIBUTES :: struct { + Size: ULONG, + VendorID: USHORT, + ProductID: USHORT, + VersionNumber: USHORT, +} +PHIDD_ATTRIBUTES :: ^HIDD_ATTRIBUTES HIDP_CAPS :: struct { Usage: USAGE, @@ -113,7 +125,32 @@ HIDP_VALUE_CAPS :: struct { } PHIDP_VALUE_CAPS :: ^HIDP_VALUE_CAPS -PHIDP_PREPARSED_DATA :: rawptr +HIDP_DATA :: struct { + DataIndex: USHORT, + Reserved: USHORT, + using _ : struct #raw_union { + RawValue: ULONG, + On: BOOLEAN, + }, +} +PHIDP_DATA :: ^HIDP_DATA + +HIDP_LINK_COLLECTION_NODE :: struct { + LinkUsage: USAGE, + LinkUsagePage: USAGE, + Parent: USHORT, + NumberOfChildren: USHORT, + NextSibling: USHORT, + FirstChild: USHORT, + CollectionType: [8]ULONG, + IsAlias: [1]ULONG, + Reserved: [23]ULONG, + UserContext: PVOID, +} +PHIDP_LINK_COLLECTION_NODE :: ^HIDP_LINK_COLLECTION_NODE + +HIDP_PREPARSED_DATA :: rawptr +PHIDP_PREPARSED_DATA :: ^HIDP_PREPARSED_DATA HIDP_REPORT_TYPE :: enum c.int { Input, @@ -122,6 +159,26 @@ HIDP_REPORT_TYPE :: enum c.int { } HIDP_STATUS_SUCCESS : NTSTATUS : 0x110000 +HIDP_STATUS_NULL : NTSTATUS : -2146369535 //0x80110001 +HIDP_STATUS_INVALID_PREPARSED_DATA : NTSTATUS : -1072627711 //0xC0110001 +HIDP_STATUS_INVALID_REPORT_TYPE : NTSTATUS : -1072627710 //0xC0110002 +HIDP_STATUS_INVALID_REPORT_LENGTH : NTSTATUS : -1072627709 //0xC0110003 +HIDP_STATUS_USAGE_NOT_FOUND : NTSTATUS : -1072627708 //0xC0110004 +HIDP_STATUS_VALUE_OUT_OF_RANGE : NTSTATUS : -1072627707 //0xC0110005 +HIDP_STATUS_BAD_LOG_PHY_VALUES : NTSTATUS : -1072627706 //0xC0100006 +HIDP_STATUS_BUFFER_TOO_SMALL : NTSTATUS : -1072627705 //0xC0110007 +HIDP_STATUS_INTERNAL_ERROR : NTSTATUS : -1072627704 //0xC0110008 +HIDP_STATUS_I8042_TRANS_UNKNOWN : NTSTATUS : -1072627703 //0xC0110009 +HIDP_STATUS_INCOMPATIBLE_REPORT_ID : NTSTATUS : -1072627702 //0xC011000A +HIDP_STATUS_NOT_VALUE_ARRAY : NTSTATUS : -1072627701 //0xC011000B +HIDP_STATUS_IS_VALUE_ARRAY : NTSTATUS : -1072627700 //0xC011000C +HIDP_STATUS_DATA_INDEX_NOT_FOUND : NTSTATUS : -1072627699 //0xC011000D +HIDP_STATUS_DATA_INDEX_OUT_OF_RANGE : NTSTATUS : -1072627698 //0xC011000E +HIDP_STATUS_BUTTON_NOT_PRESSED : NTSTATUS : -1072627697 //0xC011000F +HIDP_STATUS_REPORT_DOES_NOT_EXIST : NTSTATUS : -1072627696 //0xC0110010 +HIDP_STATUS_NOT_IMPLEMENTED : NTSTATUS : -1072627680 //0xC0110020 +HIDP_STATUS_NOT_BUTTON_ARRAY : NTSTATUS : -1072627679 //0xC0110021 +HIDP_STATUS_I8242_TRANS_UNKNOWN :: HIDP_STATUS_I8042_TRANS_UNKNOWN foreign import hid "system:hid.lib" @(default_calling_convention="system") @@ -131,4 +188,26 @@ foreign hid { HidP_GetValueCaps :: proc(ReportType: HIDP_REPORT_TYPE, ValueCaps: PHIDP_VALUE_CAPS, ValueCapsLength: PUSHORT, PreparsedData: PHIDP_PREPARSED_DATA) -> NTSTATUS --- HidP_GetUsages :: proc(ReportType: HIDP_REPORT_TYPE, UsagePage: USAGE, LinkCollection: USHORT, UsageList: PUSAGE, UsageLength: PULONG, PreparsedData: PHIDP_PREPARSED_DATA, Report: PCHAR, ReportLength: ULONG) -> NTSTATUS --- HidP_GetUsageValue :: proc(ReportType: HIDP_REPORT_TYPE, UsagePage: USAGE, LinkCollection: USHORT, Usage: USAGE, UsageValue: PULONG, PreparsedData: PHIDP_PREPARSED_DATA, Report: PCHAR, ReportLength: ULONG) -> NTSTATUS --- + HidP_GetData :: proc(ReportType: HIDP_REPORT_TYPE, DataList: PHIDP_DATA, DataLength: PULONG, PreparsedData: PHIDP_PREPARSED_DATA, Report: PCHAR, ReportLength: ULONG) -> NTSTATUS --- + HidP_GetLinkCollectionNodes :: proc(LinkCollectionNodes: PHIDP_LINK_COLLECTION_NODE, LinkCollectionNodesLength: PULONG, PreparsedData: PHIDP_PREPARSED_DATA) -> NTSTATUS --- + + HidD_GetAttributes :: proc(HidDeviceObject: HANDLE, Attributes: PHIDD_ATTRIBUTES) -> BOOLEAN --- + HidD_GetHidGuid :: proc(HidGuid: LPGUID) --- + HidD_GetPreparsedData :: proc(HidDeviceObject: HANDLE, PreparsedData: ^PHIDP_PREPARSED_DATA) -> BOOLEAN --- + HidD_FreePreparsedData :: proc(PreparsedData: PHIDP_PREPARSED_DATA) -> BOOLEAN --- + HidD_FlushQueue :: proc(HidDeviceObject: HANDLE) -> BOOLEAN --- + HidD_GetConfiguration :: proc(HidDeviceObject: HANDLE, Configuration: PHIDD_CONFIGURATION, ConfigurationLength: ULONG) -> BOOLEAN --- + HidD_SetConfiguration :: proc(HidDeviceObject: HANDLE, Configuration: PHIDD_CONFIGURATION, ConfigurationLength: ULONG) -> BOOLEAN --- + HidD_GetFeature :: proc(HidDeviceObject: HANDLE, ReportBuffer: PVOID, ReportBufferLength: ULONG) -> BOOLEAN --- + HidD_SetFeature :: proc(HidDeviceObject: HANDLE, ReportBuffer: PVOID, ReportBufferLength: ULONG) -> BOOLEAN --- + HidD_GetInputReport :: proc(HidDeviceObject: HANDLE, ReportBuffer: PVOID, ReportBufferLength: ULONG) -> BOOLEAN --- + HidD_SetOutputReport :: proc(HidDeviceObject: HANDLE, ReportBuffer: PVOID, ReportBufferLength: ULONG) -> BOOLEAN --- + HidD_GetNumInputBuffers :: proc(HidDeviceObject: HANDLE, NumberBuffers: PULONG) -> BOOLEAN --- + HidD_SetNumInputBuffers :: proc(HidDeviceObject: HANDLE, NumberBuffers: ULONG) -> BOOLEAN --- + HidD_GetPhysicalDescriptor :: proc(HidDeviceObject: HANDLE, Buffer: PVOID, BufferLength: ULONG) -> BOOLEAN --- + HidD_GetManufacturerString :: proc(HidDeviceObject: HANDLE, Buffer: PVOID, BufferLength: ULONG) -> BOOLEAN --- + HidD_GetProductString :: proc(HidDeviceObject: HANDLE, Buffer: PVOID, BufferLength: ULONG) -> BOOLEAN --- + HidD_GetIndexedString :: proc(HidDeviceObject: HANDLE, StringIndex: ULONG, Buffer: PVOID, BufferLength: ULONG) -> BOOLEAN --- + HidD_GetSerialNumberString :: proc(HidDeviceObject: HANDLE, Buffer: PVOID, BufferLength: ULONG) -> BOOLEAN --- + HidD_GetMsGenreDescriptor :: proc(HidDeviceObject: HANDLE, Buffer: PVOID, BufferLength: ULONG) -> BOOLEAN --- } diff --git a/core/sys/windows/hidusage.odin b/core/sys/windows/hidusage.odin new file mode 100644 index 000000000..a32aa7b9f --- /dev/null +++ b/core/sys/windows/hidusage.odin @@ -0,0 +1,690 @@ +// +build windows +package sys_windows + +USAGE :: distinct USHORT +PUSAGE :: ^USAGE + +HID_USAGE_PAGE_UNDEFINED :: 0x00 +HID_USAGE_PAGE_GENERIC :: 0x01 +HID_USAGE_PAGE_SIMULATION :: 0x02 +HID_USAGE_PAGE_VR :: 0x03 +HID_USAGE_PAGE_SPORT :: 0x04 +HID_USAGE_PAGE_GAME :: 0x05 +HID_USAGE_PAGE_GENERIC_DEVICE :: 0x06 +HID_USAGE_PAGE_KEYBOARD :: 0x07 +HID_USAGE_PAGE_LED :: 0x08 +HID_USAGE_PAGE_BUTTON :: 0x09 +HID_USAGE_PAGE_ORDINAL :: 0x0A +HID_USAGE_PAGE_TELEPHONY :: 0x0B +HID_USAGE_PAGE_CONSUMER :: 0x0C +HID_USAGE_PAGE_DIGITIZER :: 0x0D +HID_USAGE_PAGE_HAPTICS :: 0x0E +HID_USAGE_PAGE_PID :: 0x0F +HID_USAGE_PAGE_UNICODE :: 0x10 +HID_USAGE_PAGE_ALPHANUMERIC :: 0x14 +HID_USAGE_PAGE_SENSOR :: 0x20 +HID_USAGE_PAGE_LIGHTING_ILLUMINATION :: 0x59 +HID_USAGE_PAGE_BARCODE_SCANNER :: 0x8C +HID_USAGE_PAGE_WEIGHING_DEVICE :: 0x8D +HID_USAGE_PAGE_MAGNETIC_STRIPE_READER :: 0x8E +HID_USAGE_PAGE_CAMERA_CONTROL :: 0x90 +HID_USAGE_PAGE_ARCADE :: 0x91 +HID_USAGE_PAGE_MICROSOFT_BLUETOOTH_HANDSFREE :: 0xFFF3 +HID_USAGE_PAGE_VENDOR_DEFINED_BEGIN :: 0xFF00 +HID_USAGE_PAGE_VENDOR_DEFINED_END :: 0xFFFF + +HID_USAGE_GENERIC_POINTER :: 0x01 +HID_USAGE_GENERIC_MOUSE :: 0x02 +HID_USAGE_GENERIC_JOYSTICK :: 0x04 +HID_USAGE_GENERIC_GAMEPAD :: 0x05 +HID_USAGE_GENERIC_KEYBOARD :: 0x06 +HID_USAGE_GENERIC_KEYPAD :: 0x07 +HID_USAGE_GENERIC_MULTI_AXIS_CONTROLLER :: 0x08 +HID_USAGE_GENERIC_TABLET_PC_SYSTEM_CTL :: 0x09 +HID_USAGE_GENERIC_PORTABLE_DEVICE_CONTROL :: 0x0D +HID_USAGE_GENERIC_INTERACTIVE_CONTROL :: 0x0E +HID_USAGE_GENERIC_SYSTEM_CTL :: 0x80 + +HID_USAGE_GENERIC_X :: 0x30 +HID_USAGE_GENERIC_Y :: 0x31 +HID_USAGE_GENERIC_Z :: 0x32 +HID_USAGE_GENERIC_RX :: 0x33 +HID_USAGE_GENERIC_RY :: 0x34 +HID_USAGE_GENERIC_RZ :: 0x35 +HID_USAGE_GENERIC_SLIDER :: 0x36 +HID_USAGE_GENERIC_DIAL :: 0x37 +HID_USAGE_GENERIC_WHEEL :: 0x38 +HID_USAGE_GENERIC_HATSWITCH :: 0x39 +HID_USAGE_GENERIC_COUNTED_BUFFER :: 0x3A +HID_USAGE_GENERIC_BYTE_COUNT :: 0x3B +HID_USAGE_GENERIC_MOTION_WAKEUP :: 0x3C +HID_USAGE_GENERIC_START :: 0x3D +HID_USAGE_GENERIC_SELECT :: 0x3E +HID_USAGE_GENERIC_VX :: 0x40 +HID_USAGE_GENERIC_VY :: 0x41 +HID_USAGE_GENERIC_VZ :: 0x42 +HID_USAGE_GENERIC_VBRX :: 0x43 +HID_USAGE_GENERIC_VBRY :: 0x44 +HID_USAGE_GENERIC_VBRZ :: 0x45 +HID_USAGE_GENERIC_VNO :: 0x46 +HID_USAGE_GENERIC_FEATURE_NOTIFICATION :: 0x47 +HID_USAGE_GENERIC_RESOLUTION_MULTIPLIER :: 0x48 +HID_USAGE_GENERIC_SYSCTL_POWER :: 0x81 +HID_USAGE_GENERIC_SYSCTL_SLEEP :: 0x82 +HID_USAGE_GENERIC_SYSCTL_WAKE :: 0x83 +HID_USAGE_GENERIC_SYSCTL_CONTEXT_MENU :: 0x84 +HID_USAGE_GENERIC_SYSCTL_MAIN_MENU :: 0x85 +HID_USAGE_GENERIC_SYSCTL_APP_MENU :: 0x86 +HID_USAGE_GENERIC_SYSCTL_HELP_MENU :: 0x87 +HID_USAGE_GENERIC_SYSCTL_MENU_EXIT :: 0x88 +HID_USAGE_GENERIC_SYSCTL_MENU_SELECT :: 0x89 +HID_USAGE_GENERIC_SYSCTL_MENU_RIGHT :: 0x8A +HID_USAGE_GENERIC_SYSCTL_MENU_LEFT :: 0x8B +HID_USAGE_GENERIC_SYSCTL_MENU_UP :: 0x8C +HID_USAGE_GENERIC_SYSCTL_MENU_DOWN :: 0x8D +HID_USAGE_GENERIC_SYSCTL_COLD_RESTART :: 0x8E +HID_USAGE_GENERIC_SYSCTL_WARM_RESTART :: 0x8F +HID_USAGE_GENERIC_DPAD_UP :: 0x90 +HID_USAGE_GENERIC_DPAD_DOWN :: 0x91 +HID_USAGE_GENERIC_DPAD_RIGHT :: 0x92 +HID_USAGE_GENERIC_DPAD_LEFT :: 0x93 +HID_USAGE_GENERIC_SYSCTL_FN :: 0x97 +HID_USAGE_GENERIC_SYSCTL_FN_LOCK :: 0x98 +HID_USAGE_GENERIC_SYSCTL_FN_LOCK_INDICATOR :: 0x99 +HID_USAGE_GENERIC_SYSCTL_DISMISS_NOTIFICATION :: 0x9A +HID_USAGE_GENERIC_SYSCTL_DOCK :: 0xA0 +HID_USAGE_GENERIC_SYSCTL_UNDOCK :: 0xA1 +HID_USAGE_GENERIC_SYSCTL_SETUP :: 0xA2 +HID_USAGE_GENERIC_SYSCTL_SYS_BREAK :: 0xA3 +HID_USAGE_GENERIC_SYSCTL_SYS_DBG_BREAK :: 0xA4 +HID_USAGE_GENERIC_SYSCTL_APP_BREAK :: 0xA5 +HID_USAGE_GENERIC_SYSCTL_APP_DBG_BREAK :: 0xA6 +HID_USAGE_GENERIC_SYSCTL_MUTE :: 0xA7 +HID_USAGE_GENERIC_SYSCTL_HIBERNATE :: 0xA8 +HID_USAGE_GENERIC_SYSCTL_DISP_INVERT :: 0xB0 +HID_USAGE_GENERIC_SYSCTL_DISP_INTERNAL :: 0xB1 +HID_USAGE_GENERIC_SYSCTL_DISP_EXTERNAL :: 0xB2 +HID_USAGE_GENERIC_SYSCTL_DISP_BOTH :: 0xB3 +HID_USAGE_GENERIC_SYSCTL_DISP_DUAL :: 0xB4 +HID_USAGE_GENERIC_SYSCTL_DISP_TOGGLE :: 0xB5 +HID_USAGE_GENERIC_SYSCTL_DISP_SWAP :: 0xB6 +HID_USAGE_GENERIC_SYSCTL_DISP_AUTOSCALE :: 0xB7 +HID_USAGE_GENERIC_SYSTEM_DISPLAY_ROTATION_LOCK_BUTTON :: 0xC9 +HID_USAGE_GENERIC_SYSTEM_DISPLAY_ROTATION_LOCK_SLIDER_SWITCH :: 0xCA +HID_USAGE_GENERIC_CONTROL_ENABLE :: 0xCB + +HID_USAGE_SIMULATION_FLIGHT_SIMULATION_DEVICE :: 0x01 +HID_USAGE_SIMULATION_AUTOMOBILE_SIMULATION_DEVICE :: 0x02 +HID_USAGE_SIMULATION_TANK_SIMULATION_DEVICE :: 0x03 +HID_USAGE_SIMULATION_SPACESHIP_SIMULATION_DEVICE :: 0x04 +HID_USAGE_SIMULATION_SUBMARINE_SIMULATION_DEVICE :: 0x05 +HID_USAGE_SIMULATION_SAILING_SIMULATION_DEVICE :: 0x06 +HID_USAGE_SIMULATION_MOTORCYCLE_SIMULATION_DEVICE :: 0x07 +HID_USAGE_SIMULATION_SPORTS_SIMULATION_DEVICE :: 0x08 +HID_USAGE_SIMULATION_AIRPLANE_SIMULATION_DEVICE :: 0x09 +HID_USAGE_SIMULATION_HELICOPTER_SIMULATION_DEVICE :: 0x0A +HID_USAGE_SIMULATION_MAGIC_CARPET_SIMULATION_DEVICE :: 0x0B +HID_USAGE_SIMULATION_BICYCLE_SIMULATION_DEVICE :: 0x0C +HID_USAGE_SIMULATION_FLIGHT_CONTROL_STICK :: 0x20 +HID_USAGE_SIMULATION_FLIGHT_STICK :: 0x21 +HID_USAGE_SIMULATION_CYCLIC_CONTROL :: 0x22 +HID_USAGE_SIMULATION_CYCLIC_TRIM :: 0x23 +HID_USAGE_SIMULATION_FLIGHT_YOKE :: 0x24 +HID_USAGE_SIMULATION_TRACK_CONTROL :: 0x25 + +HID_USAGE_SIMULATION_AILERON :: 0xB0 +HID_USAGE_SIMULATION_AILERON_TRIM :: 0xB1 +HID_USAGE_SIMULATION_ANTI_TORQUE_CONTROL :: 0xB2 +HID_USAGE_SIMULATION_AUTOPIOLOT_ENABLE :: 0xB3 +HID_USAGE_SIMULATION_CHAFF_RELEASE :: 0xB4 +HID_USAGE_SIMULATION_COLLECTIVE_CONTROL :: 0xB5 +HID_USAGE_SIMULATION_DIVE_BRAKE :: 0xB6 +HID_USAGE_SIMULATION_ELECTRONIC_COUNTERMEASURES :: 0xB7 +HID_USAGE_SIMULATION_ELEVATOR :: 0xB8 +HID_USAGE_SIMULATION_ELEVATOR_TRIM :: 0xB9 +HID_USAGE_SIMULATION_RUDDER :: 0xBA +HID_USAGE_SIMULATION_THROTTLE :: 0xBB +HID_USAGE_SIMULATION_FLIGHT_COMMUNICATIONS :: 0xBC +HID_USAGE_SIMULATION_FLARE_RELEASE :: 0xBD +HID_USAGE_SIMULATION_LANDING_GEAR :: 0xBE +HID_USAGE_SIMULATION_TOE_BRAKE :: 0xBF +HID_USAGE_SIMULATION_TRIGGER :: 0xC0 +HID_USAGE_SIMULATION_WEAPONS_ARM :: 0xC1 +HID_USAGE_SIMULATION_WEAPONS_SELECT :: 0xC2 +HID_USAGE_SIMULATION_WING_FLAPS :: 0xC3 +HID_USAGE_SIMULATION_ACCELLERATOR :: 0xC4 +HID_USAGE_SIMULATION_BRAKE :: 0xC5 +HID_USAGE_SIMULATION_CLUTCH :: 0xC6 +HID_USAGE_SIMULATION_SHIFTER :: 0xC7 +HID_USAGE_SIMULATION_STEERING :: 0xC8 +HID_USAGE_SIMULATION_TURRET_DIRECTION :: 0xC9 +HID_USAGE_SIMULATION_BARREL_ELEVATION :: 0xCA +HID_USAGE_SIMULATION_DIVE_PLANE :: 0xCB +HID_USAGE_SIMULATION_BALLAST :: 0xCC +HID_USAGE_SIMULATION_BICYCLE_CRANK :: 0xCD +HID_USAGE_SIMULATION_HANDLE_BARS :: 0xCE +HID_USAGE_SIMULATION_FRONT_BRAKE :: 0xCF +HID_USAGE_SIMULATION_REAR_BRAKE :: 0xD0 + +HID_USAGE_VR_BELT :: 0x01 +HID_USAGE_VR_BODY_SUIT :: 0x02 +HID_USAGE_VR_FLEXOR :: 0x03 +HID_USAGE_VR_GLOVE :: 0x04 +HID_USAGE_VR_HEAD_TRACKER :: 0x05 +HID_USAGE_VR_HEAD_MOUNTED_DISPLAY :: 0x06 +HID_USAGE_VR_HAND_TRACKER :: 0x07 +HID_USAGE_VR_OCULOMETER :: 0x08 +HID_USAGE_VR_VEST :: 0x09 +HID_USAGE_VR_ANIMATRONIC_DEVICE :: 0x0A + +HID_USAGE_VR_STEREO_ENABLE :: 0x20 +HID_USAGE_VR_DISPLAY_ENABLE :: 0x21 + +HID_USAGE_SPORT_BASEBALL_BAT :: 0x01 +HID_USAGE_SPORT_GOLF_CLUB :: 0x02 +HID_USAGE_SPORT_ROWING_MACHINE :: 0x03 +HID_USAGE_SPORT_TREADMILL :: 0x04 +HID_USAGE_SPORT_STICK_TYPE :: 0x38 + +HID_USAGE_SPORT_OAR :: 0x30 +HID_USAGE_SPORT_SLOPE :: 0x31 +HID_USAGE_SPORT_RATE :: 0x32 +HID_USAGE_SPORT_STICK_SPEED :: 0x33 +HID_USAGE_SPORT_STICK_FACE_ANGLE :: 0x34 +HID_USAGE_SPORT_HEEL_TOE :: 0x35 +HID_USAGE_SPORT_FOLLOW_THROUGH :: 0x36 +HID_USAGE_SPORT_TEMPO :: 0x37 +HID_USAGE_SPORT_HEIGHT :: 0x39 +HID_USAGE_SPORT_PUTTER :: 0x50 +HID_USAGE_SPORT_1_IRON :: 0x51 +HID_USAGE_SPORT_2_IRON :: 0x52 +HID_USAGE_SPORT_3_IRON :: 0x53 +HID_USAGE_SPORT_4_IRON :: 0x54 +HID_USAGE_SPORT_5_IRON :: 0x55 +HID_USAGE_SPORT_6_IRON :: 0x56 +HID_USAGE_SPORT_7_IRON :: 0x57 +HID_USAGE_SPORT_8_IRON :: 0x58 +HID_USAGE_SPORT_9_IRON :: 0x59 +HID_USAGE_SPORT_10_IRON :: 0x5A +HID_USAGE_SPORT_11_IRON :: 0x5B +HID_USAGE_SPORT_SAND_WEDGE :: 0x5C +HID_USAGE_SPORT_LOFT_WEDGE :: 0x5D +HID_USAGE_SPORT_POWER_WEDGE :: 0x5E +HID_USAGE_SPORT_1_WOOD :: 0x5F +HID_USAGE_SPORT_3_WOOD :: 0x60 +HID_USAGE_SPORT_5_WOOD :: 0x61 +HID_USAGE_SPORT_7_WOOD :: 0x62 +HID_USAGE_SPORT_9_WOOD :: 0x63 + +HID_USAGE_GAME_3D_GAME_CONTROLLER :: 0x01 +HID_USAGE_GAME_PINBALL_DEVICE :: 0x02 +HID_USAGE_GAME_GUN_DEVICE :: 0x03 +HID_USAGE_GAME_POINT_OF_VIEW :: 0x20 +HID_USAGE_GAME_GUN_SELECTOR :: 0x32 +HID_USAGE_GAME_GAMEPAD_FIRE_JUMP :: 0x37 +HID_USAGE_GAME_GAMEPAD_TRIGGER :: 0x39 + +HID_USAGE_GAME_TURN_RIGHT_LEFT :: 0x21 +HID_USAGE_GAME_PITCH_FORWARD_BACK :: 0x22 +HID_USAGE_GAME_ROLL_RIGHT_LEFT :: 0x23 +HID_USAGE_GAME_MOVE_RIGHT_LEFT :: 0x24 +HID_USAGE_GAME_MOVE_FORWARD_BACK :: 0x25 +HID_USAGE_GAME_MOVE_UP_DOWN :: 0x26 +HID_USAGE_GAME_LEAN_RIGHT_LEFT :: 0x27 +HID_USAGE_GAME_LEAN_FORWARD_BACK :: 0x28 +HID_USAGE_GAME_POV_HEIGHT :: 0x29 +HID_USAGE_GAME_FLIPPER :: 0x2A +HID_USAGE_GAME_SECONDARY_FLIPPER :: 0x2B +HID_USAGE_GAME_BUMP :: 0x2C +HID_USAGE_GAME_NEW_GAME :: 0x2D +HID_USAGE_GAME_SHOOT_BALL :: 0x2E +HID_USAGE_GAME_PLAYER :: 0x2F +HID_USAGE_GAME_GUN_BOLT :: 0x30 +HID_USAGE_GAME_GUN_CLIP :: 0x31 +HID_USAGE_GAME_GUN_SINGLE_SHOT :: 0x33 +HID_USAGE_GAME_GUN_BURST :: 0x34 +HID_USAGE_GAME_GUN_AUTOMATIC :: 0x35 +HID_USAGE_GAME_GUN_SAFETY :: 0x36 + +HID_USAGE_GENERIC_DEVICE_BATTERY_STRENGTH :: 0x20 +HID_USAGE_GENERIC_DEVICE_WIRELESS_CHANNEL :: 0x21 +HID_USAGE_GENERIC_DEVICE_WIRELESS_ID :: 0x22 +HID_USAGE_GENERIC_DEVICE_DISCOVER_WIRELESS_CONTROL :: 0x23 +HID_USAGE_GENERIC_DEVICE_SECURITY_CODE_CHAR_ENTERED :: 0x24 +HID_USAGE_GENERIC_DEVICE_SECURITY_CODE_CHAR_ERASED :: 0x25 +HID_USAGE_GENERIC_DEVICE_SECURITY_CODE_CLEARED :: 0x26 + +// Error "keys" +HID_USAGE_KEYBOARD_NOEVENT :: 0x00 +HID_USAGE_KEYBOARD_ROLLOVER :: 0x01 +HID_USAGE_KEYBOARD_POSTFAIL :: 0x02 +HID_USAGE_KEYBOARD_UNDEFINED :: 0x03 + +// Letters +HID_USAGE_KEYBOARD_aA :: 0x04 +HID_USAGE_KEYBOARD_zZ :: 0x1D + +// Numbers +HID_USAGE_KEYBOARD_ONE :: 0x1E +HID_USAGE_KEYBOARD_ZERO :: 0x27 + +// Modifier Keys +HID_USAGE_KEYBOARD_LCTRL :: 0xE0 +HID_USAGE_KEYBOARD_LSHFT :: 0xE1 +HID_USAGE_KEYBOARD_LALT :: 0xE2 +HID_USAGE_KEYBOARD_LGUI :: 0xE3 +HID_USAGE_KEYBOARD_RCTRL :: 0xE4 +HID_USAGE_KEYBOARD_RSHFT :: 0xE5 +HID_USAGE_KEYBOARD_RALT :: 0xE6 +HID_USAGE_KEYBOARD_RGUI :: 0xE7 +HID_USAGE_KEYBOARD_SCROLL_LOCK :: 0x47 +HID_USAGE_KEYBOARD_NUM_LOCK :: 0x53 +HID_USAGE_KEYBOARD_CAPS_LOCK :: 0x39 + +// Function keys +HID_USAGE_KEYBOARD_F1 :: 0x3A +HID_USAGE_KEYBOARD_F2 :: 0x3B +HID_USAGE_KEYBOARD_F3 :: 0x3C +HID_USAGE_KEYBOARD_F4 :: 0x3D +HID_USAGE_KEYBOARD_F5 :: 0x3E +HID_USAGE_KEYBOARD_F6 :: 0x3F +HID_USAGE_KEYBOARD_F7 :: 0x40 +HID_USAGE_KEYBOARD_F8 :: 0x41 +HID_USAGE_KEYBOARD_F9 :: 0x42 +HID_USAGE_KEYBOARD_F10 :: 0x43 +HID_USAGE_KEYBOARD_F11 :: 0x44 +HID_USAGE_KEYBOARD_F12 :: 0x45 +HID_USAGE_KEYBOARD_F13 :: 0x68 +HID_USAGE_KEYBOARD_F14 :: 0x69 +HID_USAGE_KEYBOARD_F15 :: 0x6A +HID_USAGE_KEYBOARD_F16 :: 0x6B +HID_USAGE_KEYBOARD_F17 :: 0x6C +HID_USAGE_KEYBOARD_F18 :: 0x6D +HID_USAGE_KEYBOARD_F19 :: 0x6E +HID_USAGE_KEYBOARD_F20 :: 0x6F +HID_USAGE_KEYBOARD_F21 :: 0x70 +HID_USAGE_KEYBOARD_F22 :: 0x71 +HID_USAGE_KEYBOARD_F23 :: 0x72 +HID_USAGE_KEYBOARD_F24 :: 0x73 + +HID_USAGE_KEYBOARD_RETURN :: 0x28 +HID_USAGE_KEYBOARD_ESCAPE :: 0x29 +HID_USAGE_KEYBOARD_DELETE :: 0x2A + +HID_USAGE_KEYBOARD_PRINT_SCREEN :: 0x46 +HID_USAGE_KEYBOARD_DELETE_FORWARD :: 0x4C + +// Numeric Keypad +HID_USAGE_KEYBOARD_KEYPAD_1_AND_END :: 0x59 +HID_USAGE_KEYBOARD_KEYPAD_0_AND_INSERT :: 0x62 + +HID_USAGE_LED_NUM_LOCK :: 0x01 +HID_USAGE_LED_CAPS_LOCK :: 0x02 +HID_USAGE_LED_SCROLL_LOCK :: 0x03 +HID_USAGE_LED_COMPOSE :: 0x04 +HID_USAGE_LED_KANA :: 0x05 +HID_USAGE_LED_POWER :: 0x06 +HID_USAGE_LED_SHIFT :: 0x07 +HID_USAGE_LED_DO_NOT_DISTURB :: 0x08 +HID_USAGE_LED_MUTE :: 0x09 +HID_USAGE_LED_TONE_ENABLE :: 0x0A +HID_USAGE_LED_HIGH_CUT_FILTER :: 0x0B +HID_USAGE_LED_LOW_CUT_FILTER :: 0x0C +HID_USAGE_LED_EQUALIZER_ENABLE :: 0x0D +HID_USAGE_LED_SOUND_FIELD_ON :: 0x0E +HID_USAGE_LED_SURROUND_FIELD_ON :: 0x0F +HID_USAGE_LED_REPEAT :: 0x10 +HID_USAGE_LED_STEREO :: 0x11 +HID_USAGE_LED_SAMPLING_RATE_DETECT :: 0x12 +HID_USAGE_LED_SPINNING :: 0x13 +HID_USAGE_LED_CAV :: 0x14 +HID_USAGE_LED_CLV :: 0x15 +HID_USAGE_LED_RECORDING_FORMAT_DET :: 0x16 +HID_USAGE_LED_OFF_HOOK :: 0x17 +HID_USAGE_LED_RING :: 0x18 +HID_USAGE_LED_MESSAGE_WAITING :: 0x19 +HID_USAGE_LED_DATA_MODE :: 0x1A +HID_USAGE_LED_BATTERY_OPERATION :: 0x1B +HID_USAGE_LED_BATTERY_OK :: 0x1C +HID_USAGE_LED_BATTERY_LOW :: 0x1D +HID_USAGE_LED_SPEAKER :: 0x1E +HID_USAGE_LED_HEAD_SET :: 0x1F +HID_USAGE_LED_HOLD :: 0x20 +HID_USAGE_LED_MICROPHONE :: 0x21 +HID_USAGE_LED_COVERAGE :: 0x22 +HID_USAGE_LED_NIGHT_MODE :: 0x23 +HID_USAGE_LED_SEND_CALLS :: 0x24 +HID_USAGE_LED_CALL_PICKUP :: 0x25 +HID_USAGE_LED_CONFERENCE :: 0x26 +HID_USAGE_LED_STAND_BY :: 0x27 +HID_USAGE_LED_CAMERA_ON :: 0x28 +HID_USAGE_LED_CAMERA_OFF :: 0x29 +HID_USAGE_LED_ON_LINE :: 0x2A +HID_USAGE_LED_OFF_LINE :: 0x2B +HID_USAGE_LED_BUSY :: 0x2C +HID_USAGE_LED_READY :: 0x2D +HID_USAGE_LED_PAPER_OUT :: 0x2E +HID_USAGE_LED_PAPER_JAM :: 0x2F +HID_USAGE_LED_REMOTE :: 0x30 +HID_USAGE_LED_FORWARD :: 0x31 +HID_USAGE_LED_REVERSE :: 0x32 +HID_USAGE_LED_STOP :: 0x33 +HID_USAGE_LED_REWIND :: 0x34 +HID_USAGE_LED_FAST_FORWARD :: 0x35 +HID_USAGE_LED_PLAY :: 0x36 +HID_USAGE_LED_PAUSE :: 0x37 +HID_USAGE_LED_RECORD :: 0x38 +HID_USAGE_LED_ERROR :: 0x39 +HID_USAGE_LED_SELECTED_INDICATOR :: 0x3A +HID_USAGE_LED_IN_USE_INDICATOR :: 0x3B +HID_USAGE_LED_MULTI_MODE_INDICATOR :: 0x3C +HID_USAGE_LED_INDICATOR_ON :: 0x3D +HID_USAGE_LED_INDICATOR_FLASH :: 0x3E +HID_USAGE_LED_INDICATOR_SLOW_BLINK :: 0x3F +HID_USAGE_LED_INDICATOR_FAST_BLINK :: 0x40 +HID_USAGE_LED_INDICATOR_OFF :: 0x41 +HID_USAGE_LED_FLASH_ON_TIME :: 0x42 +HID_USAGE_LED_SLOW_BLINK_ON_TIME :: 0x43 +HID_USAGE_LED_SLOW_BLINK_OFF_TIME :: 0x44 +HID_USAGE_LED_FAST_BLINK_ON_TIME :: 0x45 +HID_USAGE_LED_FAST_BLINK_OFF_TIME :: 0x46 +HID_USAGE_LED_INDICATOR_COLOR :: 0x47 +HID_USAGE_LED_RED :: 0x48 +HID_USAGE_LED_GREEN :: 0x49 +HID_USAGE_LED_AMBER :: 0x4A +HID_USAGE_LED_GENERIC_INDICATOR :: 0x4B +HID_USAGE_LED_SYSTEM_SUSPEND :: 0x4C +HID_USAGE_LED_EXTERNAL_POWER :: 0x4D + +HID_USAGE_TELEPHONY_PHONE :: 0x01 +HID_USAGE_TELEPHONY_ANSWERING_MACHINE :: 0x02 +HID_USAGE_TELEPHONY_MESSAGE_CONTROLS :: 0x03 +HID_USAGE_TELEPHONY_HANDSET :: 0x04 +HID_USAGE_TELEPHONY_HEADSET :: 0x05 +HID_USAGE_TELEPHONY_KEYPAD :: 0x06 +HID_USAGE_TELEPHONY_PROGRAMMABLE_BUTTON :: 0x07 +HID_USAGE_TELEPHONY_REDIAL :: 0x24 +HID_USAGE_TELEPHONY_TRANSFER :: 0x25 +HID_USAGE_TELEPHONY_DROP :: 0x26 +HID_USAGE_TELEPHONY_LINE :: 0x2A +HID_USAGE_TELEPHONY_RING_ENABLE :: 0x2D +HID_USAGE_TELEPHONY_SEND :: 0x31 +HID_USAGE_TELEPHONY_KEYPAD_0 :: 0xB0 +HID_USAGE_TELEPHONY_KEYPAD_D :: 0xBF +HID_USAGE_TELEPHONY_HOST_AVAILABLE :: 0xF1 + +HID_USAGE_CONSUMERCTRL :: 0x01 + +// channel +HID_USAGE_CONSUMER_CHANNEL_INCREMENT :: 0x9C +HID_USAGE_CONSUMER_CHANNEL_DECREMENT :: 0x9D + +// transport control +HID_USAGE_CONSUMER_PLAY :: 0xB0 +HID_USAGE_CONSUMER_PAUSE :: 0xB1 +HID_USAGE_CONSUMER_RECORD :: 0xB2 +HID_USAGE_CONSUMER_FAST_FORWARD :: 0xB3 +HID_USAGE_CONSUMER_REWIND :: 0xB4 +HID_USAGE_CONSUMER_SCAN_NEXT_TRACK :: 0xB5 +HID_USAGE_CONSUMER_SCAN_PREV_TRACK :: 0xB6 +HID_USAGE_CONSUMER_STOP :: 0xB7 +HID_USAGE_CONSUMER_PLAY_PAUSE :: 0xCD + +// GameDVR +HID_USAGE_CONSUMER_GAMEDVR_OPEN_GAMEBAR :: 0xD0 +HID_USAGE_CONSUMER_GAMEDVR_TOGGLE_RECORD :: 0xD1 +HID_USAGE_CONSUMER_GAMEDVR_RECORD_CLIP :: 0xD2 +HID_USAGE_CONSUMER_GAMEDVR_SCREENSHOT :: 0xD3 +HID_USAGE_CONSUMER_GAMEDVR_TOGGLE_INDICATOR :: 0xD4 +HID_USAGE_CONSUMER_GAMEDVR_TOGGLE_MICROPHONE :: 0xD5 +HID_USAGE_CONSUMER_GAMEDVR_TOGGLE_CAMERA :: 0xD6 +HID_USAGE_CONSUMER_GAMEDVR_TOGGLE_BROADCAST :: 0xD7 + +// audio +HID_USAGE_CONSUMER_VOLUME :: 0xE0 +HID_USAGE_CONSUMER_BALANCE :: 0xE1 +HID_USAGE_CONSUMER_MUTE :: 0xE2 +HID_USAGE_CONSUMER_BASS :: 0xE3 +HID_USAGE_CONSUMER_TREBLE :: 0xE4 +HID_USAGE_CONSUMER_BASS_BOOST :: 0xE5 +HID_USAGE_CONSUMER_SURROUND_MODE :: 0xE6 +HID_USAGE_CONSUMER_LOUDNESS :: 0xE7 +HID_USAGE_CONSUMER_MPX :: 0xE8 +HID_USAGE_CONSUMER_VOLUME_INCREMENT :: 0xE9 +HID_USAGE_CONSUMER_VOLUME_DECREMENT :: 0xEA + +// supplementary audio +HID_USAGE_CONSUMER_BASS_INCREMENT :: 0x152 +HID_USAGE_CONSUMER_BASS_DECREMENT :: 0x153 +HID_USAGE_CONSUMER_TREBLE_INCREMENT :: 0x154 +HID_USAGE_CONSUMER_TREBLE_DECREMENT :: 0x155 + +// Application Launch +HID_USAGE_CONSUMER_AL_CONFIGURATION :: 0x183 +HID_USAGE_CONSUMER_AL_EMAIL :: 0x18A +HID_USAGE_CONSUMER_AL_CALCULATOR :: 0x192 +HID_USAGE_CONSUMER_AL_BROWSER :: 0x194 +HID_USAGE_CONSUMER_AL_SEARCH :: 0x1C6 + +// Application Control +HID_USAGE_CONSUMER_AC_SEARCH :: 0x221 +HID_USAGE_CONSUMER_AC_GOTO :: 0x222 +HID_USAGE_CONSUMER_AC_HOME :: 0x223 +HID_USAGE_CONSUMER_AC_BACK :: 0x224 +HID_USAGE_CONSUMER_AC_FORWARD :: 0x225 +HID_USAGE_CONSUMER_AC_STOP :: 0x226 +HID_USAGE_CONSUMER_AC_REFRESH :: 0x227 +HID_USAGE_CONSUMER_AC_PREVIOUS :: 0x228 +HID_USAGE_CONSUMER_AC_NEXT :: 0x229 +HID_USAGE_CONSUMER_AC_BOOKMARKS :: 0x22A +HID_USAGE_CONSUMER_AC_PAN :: 0x238 + +// Keyboard Extended Attributes (defined on consumer page in HUTRR42) +HID_USAGE_CONSUMER_EXTENDED_KEYBOARD_ATTRIBUTES_COLLECTION :: 0x2C0 +HID_USAGE_CONSUMER_KEYBOARD_FORM_FACTOR :: 0x2C1 +HID_USAGE_CONSUMER_KEYBOARD_KEY_TYPE :: 0x2C2 +HID_USAGE_CONSUMER_KEYBOARD_PHYSICAL_LAYOUT :: 0x2C3 +HID_USAGE_CONSUMER_VENDOR_SPECIFIC_KEYBOARD_PHYSICAL_LAYOUT :: 0x2C4 +HID_USAGE_CONSUMER_KEYBOARD_IETF_LANGUAGE_TAG_INDEX :: 0x2C5 +HID_USAGE_CONSUMER_IMPLEMENTED_KEYBOARD_INPUT_ASSIST_CONTROLS :: 0x2C6 + +HID_USAGE_DIGITIZER_DIGITIZER :: 0x01 +HID_USAGE_DIGITIZER_PEN :: 0x02 +HID_USAGE_DIGITIZER_LIGHT_PEN :: 0x03 +HID_USAGE_DIGITIZER_TOUCH_SCREEN :: 0x04 +HID_USAGE_DIGITIZER_TOUCH_PAD :: 0x05 +HID_USAGE_DIGITIZER_WHITE_BOARD :: 0x06 +HID_USAGE_DIGITIZER_COORD_MEASURING :: 0x07 +HID_USAGE_DIGITIZER_3D_DIGITIZER :: 0x08 +HID_USAGE_DIGITIZER_STEREO_PLOTTER :: 0x09 +HID_USAGE_DIGITIZER_ARTICULATED_ARM :: 0x0A +HID_USAGE_DIGITIZER_ARMATURE :: 0x0B +HID_USAGE_DIGITIZER_MULTI_POINT :: 0x0C +HID_USAGE_DIGITIZER_FREE_SPACE_WAND :: 0x0D +HID_USAGE_DIGITIZER_HEAT_MAP :: 0x0F +HID_USAGE_DIGITIZER_STYLUS :: 0x20 +HID_USAGE_DIGITIZER_PUCK :: 0x21 +HID_USAGE_DIGITIZER_FINGER :: 0x22 +HID_USAGE_DIGITIZER_TABLET_FUNC_KEYS :: 0x39 +HID_USAGE_DIGITIZER_PROG_CHANGE_KEYS :: 0x3A + +HID_USAGE_DIGITIZER_TIP_PRESSURE :: 0x30 +HID_USAGE_DIGITIZER_BARREL_PRESSURE :: 0x31 +HID_USAGE_DIGITIZER_IN_RANGE :: 0x32 +HID_USAGE_DIGITIZER_TOUCH :: 0x33 +HID_USAGE_DIGITIZER_UNTOUCH :: 0x34 +HID_USAGE_DIGITIZER_TAP :: 0x35 +HID_USAGE_DIGITIZER_QUALITY :: 0x36 +HID_USAGE_DIGITIZER_DATA_VALID :: 0x37 +HID_USAGE_DIGITIZER_TRANSDUCER_INDEX :: 0x38 +HID_USAGE_DIGITIZER_BATTERY_STRENGTH :: 0x3B +HID_USAGE_DIGITIZER_INVERT :: 0x3C +HID_USAGE_DIGITIZER_X_TILT :: 0x3D +HID_USAGE_DIGITIZER_Y_TILT :: 0x3E +HID_USAGE_DIGITIZER_AZIMUTH :: 0x3F +HID_USAGE_DIGITIZER_ALTITUDE :: 0x40 +HID_USAGE_DIGITIZER_TWIST :: 0x41 +HID_USAGE_DIGITIZER_TIP_SWITCH :: 0x42 +HID_USAGE_DIGITIZER_SECONDARY_TIP_SWITCH :: 0x43 +HID_USAGE_DIGITIZER_BARREL_SWITCH :: 0x44 +HID_USAGE_DIGITIZER_ERASER :: 0x45 +HID_USAGE_DIGITIZER_TABLET_PICK :: 0x46 +HID_USAGE_DIGITIZER_TRANSDUCER_SERIAL :: 0x5B +HID_USAGE_DIGITIZER_HEAT_MAP_PROTOCOL_VENDOR_ID :: 0x6A +HID_USAGE_DIGITIZER_HEAT_MAP_PROTOCOL_VERSION :: 0x6B +HID_USAGE_DIGITIZER_HEAT_MAP_FRAME_DATA :: 0x6C +HID_USAGE_DIGITIZER_TRANSDUCER_SERIAL_PART2 :: 0x6E +HID_USAGE_DIGITIZER_TRANSDUCER_VENDOR :: 0x91 +HID_USAGE_DIGITIZER_TRANSDUCER_PRODUCT :: 0x92 +HID_USAGE_DIGITIZER_TRANSDUCER_CONNECTED :: 0xA2 + +HID_USAGE_HAPTICS_SIMPLE_CONTROLLER :: 0x01 + +HID_USAGE_HAPTICS_WAVEFORM_LIST :: 0x10 +HID_USAGE_HAPTICS_DURATION_LIST :: 0x11 + +HID_USAGE_HAPTICS_AUTO_TRIGGER :: 0x20 +HID_USAGE_HAPTICS_MANUAL_TRIGGER :: 0x21 +HID_USAGE_HAPTICS_AUTO_ASSOCIATED_CONTROL :: 0x22 +HID_USAGE_HAPTICS_INTENSITY :: 0x23 +HID_USAGE_HAPTICS_REPEAT_COUNT :: 0x24 +HID_USAGE_HAPTICS_RETRIGGER_PERIOD :: 0x25 +HID_USAGE_HAPTICS_WAVEFORM_VENDOR_PAGE :: 0x26 +HID_USAGE_HAPTICS_WAVEFORM_VENDOR_ID :: 0x27 +HID_USAGE_HAPTICS_WAVEFORM_CUTOFF_TIME :: 0x28 + +// Waveform types +HID_USAGE_HAPTICS_WAVEFORM_BEGIN :: 0x1000 +HID_USAGE_HAPTICS_WAVEFORM_STOP :: 0x1001 +HID_USAGE_HAPTICS_WAVEFORM_NULL :: 0x1002 +HID_USAGE_HAPTICS_WAVEFORM_CLICK :: 0x1003 +HID_USAGE_HAPTICS_WAVEFORM_BUZZ :: 0x1004 +HID_USAGE_HAPTICS_WAVEFORM_RUMBLE :: 0x1005 +HID_USAGE_HAPTICS_WAVEFORM_PRESS :: 0x1006 +HID_USAGE_HAPTICS_WAVEFORM_RELEASE :: 0x1007 +HID_USAGE_HAPTICS_WAVEFORM_END :: 0x1FFF + +HID_USAGE_HAPTICS_WAVEFORM_VENDOR_BEGIN :: 0x2000 +HID_USAGE_HAPTICS_WAVEFORM_VENDOR_END :: 0x2FFF + +HID_USAGE_ALPHANUMERIC_ALPHANUMERIC_DISPLAY :: 0x01 +HID_USAGE_ALPHANUMERIC_BITMAPPED_DISPLAY :: 0x02 +HID_USAGE_ALPHANUMERIC_DISPLAY_ATTRIBUTES_REPORT :: 0x20 +HID_USAGE_ALPHANUMERIC_DISPLAY_CONTROL_REPORT :: 0x24 +HID_USAGE_ALPHANUMERIC_CHARACTER_REPORT :: 0x2B +HID_USAGE_ALPHANUMERIC_DISPLAY_STATUS :: 0x2D +HID_USAGE_ALPHANUMERIC_CURSOR_POSITION_REPORT :: 0x32 +HID_USAGE_ALPHANUMERIC_FONT_REPORT :: 0x3B +HID_USAGE_ALPHANUMERIC_FONT_DATA :: 0x3C +HID_USAGE_ALPHANUMERIC_CHARACTER_ATTRIBUTE :: 0x48 +HID_USAGE_ALPHANUMERIC_PALETTE_REPORT :: 0x85 +HID_USAGE_ALPHANUMERIC_PALETTE_DATA :: 0x88 +HID_USAGE_ALPHANUMERIC_BLIT_REPORT :: 0x8A +HID_USAGE_ALPHANUMERIC_BLIT_DATA :: 0x8F +HID_USAGE_ALPHANUMERIC_SOFT_BUTTON :: 0x90 + +HID_USAGE_ALPHANUMERIC_ASCII_CHARACTER_SET :: 0x21 +HID_USAGE_ALPHANUMERIC_DATA_READ_BACK :: 0x22 +HID_USAGE_ALPHANUMERIC_FONT_READ_BACK :: 0x23 +HID_USAGE_ALPHANUMERIC_CLEAR_DISPLAY :: 0x25 +HID_USAGE_ALPHANUMERIC_DISPLAY_ENABLE :: 0x26 +HID_USAGE_ALPHANUMERIC_SCREEN_SAVER_DELAY :: 0x27 +HID_USAGE_ALPHANUMERIC_SCREEN_SAVER_ENABLE :: 0x28 +HID_USAGE_ALPHANUMERIC_VERTICAL_SCROLL :: 0x29 +HID_USAGE_ALPHANUMERIC_HORIZONTAL_SCROLL :: 0x2A +HID_USAGE_ALPHANUMERIC_DISPLAY_DATA :: 0x2C +HID_USAGE_ALPHANUMERIC_STATUS_NOT_READY :: 0x2E +HID_USAGE_ALPHANUMERIC_STATUS_READY :: 0x2F +HID_USAGE_ALPHANUMERIC_ERR_NOT_A_LOADABLE_CHARACTER :: 0x30 +HID_USAGE_ALPHANUMERIC_ERR_FONT_DATA_CANNOT_BE_READ :: 0x31 +HID_USAGE_ALPHANUMERIC_ROW :: 0x33 +HID_USAGE_ALPHANUMERIC_COLUMN :: 0x34 +HID_USAGE_ALPHANUMERIC_ROWS :: 0x35 +HID_USAGE_ALPHANUMERIC_COLUMNS :: 0x36 +HID_USAGE_ALPHANUMERIC_CURSOR_PIXEL_POSITIONING :: 0x37 +HID_USAGE_ALPHANUMERIC_CURSOR_MODE :: 0x38 +HID_USAGE_ALPHANUMERIC_CURSOR_ENABLE :: 0x39 +HID_USAGE_ALPHANUMERIC_CURSOR_BLINK :: 0x3A +HID_USAGE_ALPHANUMERIC_CHAR_WIDTH :: 0x3D +HID_USAGE_ALPHANUMERIC_CHAR_HEIGHT :: 0x3E +HID_USAGE_ALPHANUMERIC_CHAR_SPACING_HORIZONTAL :: 0x3F +HID_USAGE_ALPHANUMERIC_CHAR_SPACING_VERTICAL :: 0x40 +HID_USAGE_ALPHANUMERIC_UNICODE_CHAR_SET :: 0x41 +HID_USAGE_ALPHANUMERIC_FONT_7_SEGMENT :: 0x42 +HID_USAGE_ALPHANUMERIC_7_SEGMENT_DIRECT_MAP :: 0x43 +HID_USAGE_ALPHANUMERIC_FONT_14_SEGMENT :: 0x44 +HID_USAGE_ALPHANUMERIC_14_SEGMENT_DIRECT_MAP :: 0x45 +HID_USAGE_ALPHANUMERIC_DISPLAY_BRIGHTNESS :: 0x46 +HID_USAGE_ALPHANUMERIC_DISPLAY_CONTRAST :: 0x47 +HID_USAGE_ALPHANUMERIC_ATTRIBUTE_READBACK :: 0x49 +HID_USAGE_ALPHANUMERIC_ATTRIBUTE_DATA :: 0x4A +HID_USAGE_ALPHANUMERIC_CHAR_ATTR_ENHANCE :: 0x4B +HID_USAGE_ALPHANUMERIC_CHAR_ATTR_UNDERLINE :: 0x4C +HID_USAGE_ALPHANUMERIC_CHAR_ATTR_BLINK :: 0x4D +HID_USAGE_ALPHANUMERIC_BITMAP_SIZE_X :: 0x80 +HID_USAGE_ALPHANUMERIC_BITMAP_SIZE_Y :: 0x81 +HID_USAGE_ALPHANUMERIC_BIT_DEPTH_FORMAT :: 0x83 +HID_USAGE_ALPHANUMERIC_DISPLAY_ORIENTATION :: 0x84 +HID_USAGE_ALPHANUMERIC_PALETTE_DATA_SIZE :: 0x86 +HID_USAGE_ALPHANUMERIC_PALETTE_DATA_OFFSET :: 0x87 +HID_USAGE_ALPHANUMERIC_BLIT_RECTANGLE_X1 :: 0x8B +HID_USAGE_ALPHANUMERIC_BLIT_RECTANGLE_Y1 :: 0x8C +HID_USAGE_ALPHANUMERIC_BLIT_RECTANGLE_X2 :: 0x8D +HID_USAGE_ALPHANUMERIC_BLIT_RECTANGLE_Y2 :: 0x8E +HID_USAGE_ALPHANUMERIC_SOFT_BUTTON_ID :: 0x91 +HID_USAGE_ALPHANUMERIC_SOFT_BUTTON_SIDE :: 0x92 +HID_USAGE_ALPHANUMERIC_SOFT_BUTTON_OFFSET1 :: 0x93 +HID_USAGE_ALPHANUMERIC_SOFT_BUTTON_OFFSET2 :: 0x94 +HID_USAGE_ALPHANUMERIC_SOFT_BUTTON_REPORT :: 0x95 + +HID_USAGE_LAMPARRAY :: 0x01 +HID_USAGE_LAMPARRAY_ATTRBIUTES_REPORT :: 0x02 +HID_USAGE_LAMPARRAY_LAMP_COUNT :: 0x03 +HID_USAGE_LAMPARRAY_BOUNDING_BOX_WIDTH_IN_MICROMETERS :: 0x04 +HID_USAGE_LAMPARRAY_BOUNDING_BOX_HEIGHT_IN_MICROMETERS :: 0x05 +HID_USAGE_LAMPARRAY_BOUNDING_BOX_DEPTH_IN_MICROMETERS :: 0x06 +HID_USAGE_LAMPARRAY_KIND :: 0x07 +HID_USAGE_LAMPARRAY_MIN_UPDATE_INTERVAL_IN_MICROSECONDS :: 0x08 + +// 0x09 - 0x1F Reserved + +HID_USAGE_LAMPARRAY_LAMP_ATTRIBUTES_REQUEST_REPORT :: 0x20 +HID_USAGE_LAMPARRAY_LAMP_ID :: 0x21 +HID_USAGE_LAMPARRAY_LAMP_ATTRIBUTES_RESPONSE_REPORT :: 0x22 +HID_USAGE_LAMPARRAY_POSITION_X_IN_MICROMETERS :: 0x23 +HID_USAGE_LAMPARRAY_POSITION_Y_IN_MICROMETERS :: 0x24 +HID_USAGE_LAMPARRAY_POSITION_Z_IN_MICROMETERS :: 0x25 +HID_USAGE_LAMPARRAY_LAMP_PURPOSES :: 0x26 +HID_USAGE_LAMPARRAY_UPDATE_LATENCY_IN_MICROSECONDS :: 0x27 +HID_USAGE_LAMPARRAY_RED_LEVEL_COUNT :: 0x28 +HID_USAGE_LAMPARRAY_GREEN_LEVEL_COUNT :: 0x29 +HID_USAGE_LAMPARRAY_BLUE_LEVEL_COUNT :: 0x2A +HID_USAGE_LAMPARRAY_INTENSITY_LEVEL_COUNT :: 0x2B +HID_USAGE_LAMPARRAY_IS_PROGRAMMABLE :: 0x2C +HID_USAGE_LAMPARRAY_INPUT_BINDING :: 0x2D + +// 0x2E - 0x4F Reserved + +HID_USAGE_LAMPARRAY_LAMP_MULTI_UPDATE_REPORT :: 0x50 +HID_USAGE_LAMPARRAY_LAMP_RED_UPDATE_CHANNEL :: 0x51 +HID_USAGE_LAMPARRAY_LAMP_GREEN_UPDATE_CHANNEL :: 0x52 +HID_USAGE_LAMPARRAY_LAMP_BLUE_UPDATE_CHANNEL :: 0x53 +HID_USAGE_LAMPARRAY_LAMP_INTENSITY_UPDATE_CHANNEL :: 0x54 +HID_USAGE_LAMPARRAY_LAMP_UPDATE_FLAGS :: 0x55 + +// 0x55 - 0x5F Reserved + +HID_USAGE_LAMPARRAY_LAMP_RANGE_UPDATE_REPORT :: 0x60 +HID_USAGE_LAMPARRAY_LAMP_ID_START :: 0x61 +HID_USAGE_LAMPARRAY_LAMP_ID_END :: 0x62 + +// 0x63 - 0x6F Reserved + +HID_USAGE_LAMPARRAY_CONTROL_REPORT :: 0x70 +HID_USAGE_LAMPARRAY_AUTONOMOUS_MODE :: 0x71 + +HID_USAGE_CAMERA_AUTO_FOCUS :: 0x20 +HID_USAGE_CAMERA_SHUTTER :: 0x21 + +HID_USAGE_MS_BTH_HF_DIALNUMBER :: 0x21 +HID_USAGE_MS_BTH_HF_DIALMEMORY :: 0x22 diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin old mode 100644 new mode 100755 index 10cc80041..92d561c30 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -38,17 +38,36 @@ foreign kernel32 { lpNumberOfCharsWritten: LPDWORD, lpReserved: LPVOID) -> BOOL --- + PeekConsoleInputW :: proc(hConsoleInput: HANDLE, + lpBuffer: ^INPUT_RECORD, + nLength: DWORD, + lpNumberOfEventsRead: LPDWORD) -> BOOL --- + + ReadConsoleInputW :: proc(hConsoleInput: HANDLE, + lpBuffer: ^INPUT_RECORD, + nLength: DWORD, + lpNumberOfEventsRead: LPDWORD) -> BOOL --- + + // https://learn.microsoft.com/en-us/windows/console/getnumberofconsoleinputevents + GetNumberOfConsoleInputEvents :: proc(hConsoleInput: HANDLE, lpcNumberOfEvents: LPDWORD) -> BOOL --- + GetConsoleMode :: proc(hConsoleHandle: HANDLE, lpMode: LPDWORD) -> BOOL --- SetConsoleMode :: proc(hConsoleHandle: HANDLE, dwMode: DWORD) -> BOOL --- SetConsoleCursorPosition :: proc(hConsoleHandle: HANDLE, - dwCursorPosition: COORD) -> BOOL --- + dwCursorPosition: COORD) -> BOOL --- SetConsoleTextAttribute :: proc(hConsoleOutput: HANDLE, - wAttributes: WORD) -> BOOL --- + wAttributes: WORD) -> BOOL --- + GetConsoleCP :: proc() -> UINT --- + SetConsoleCP :: proc(wCodePageID: UINT) -> BOOL --- + GetConsoleOutputCP :: proc() -> UINT --- SetConsoleOutputCP :: proc(wCodePageID: UINT) -> BOOL --- - + FlushConsoleInputBuffer :: proc(hConsoleInput: HANDLE) -> BOOL --- + GetFileInformationByHandle :: proc(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL --- + + SetHandleInformation :: proc(hObject: HANDLE, dwMask: DWORD, dwFlags: DWORD) -> BOOL --- @@ -64,6 +83,7 @@ foreign kernel32 { RemoveVectoredContinueHandler :: proc(Handle: LPVOID) -> DWORD --- RaiseException :: proc(dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD, lpArguments: ^ULONG_PTR) -> ! --- + SetUnhandledExceptionFilter :: proc(lpTopLevelExceptionFilter: LPTOP_LEVEL_EXCEPTION_FILTER) -> LPTOP_LEVEL_EXCEPTION_FILTER --- CreateHardLinkW :: proc(lpSymlinkFileName: LPCWSTR, lpTargetFileName: LPCWSTR, @@ -84,6 +104,12 @@ foreign kernel32 { RemoveDirectoryW :: proc(lpPathName: LPCWSTR) -> BOOL --- SetFileAttributesW :: proc(lpFileName: LPCWSTR, dwFileAttributes: DWORD) -> BOOL --- SetLastError :: proc(dwErrCode: DWORD) --- + GetCommTimeouts :: proc(handle: HANDLE, timeouts: ^COMMTIMEOUTS) -> BOOL --- + SetCommTimeouts :: proc(handle: HANDLE, timeouts: ^COMMTIMEOUTS) -> BOOL --- + ClearCommError :: proc(hFile: HANDLE, lpErrors: ^Com_Error, lpStat: ^COMSTAT) -> BOOL --- + GetCommState :: proc(handle: HANDLE, dcb: ^DCB) -> BOOL --- + SetCommState :: proc(handle: HANDLE, dcb: ^DCB) -> BOOL --- + GetCommPorts :: proc(lpPortNumbers: PULONG, uPortNumbersCount: ULONG, puPortNumbersFound: PULONG) -> ULONG --- GetCommandLineW :: proc() -> LPCWSTR --- GetTempPathW :: proc(nBufferLength: DWORD, lpBuffer: LPCWSTR) -> DWORD --- GetCurrentProcess :: proc() -> HANDLE --- @@ -207,6 +233,12 @@ foreign kernel32 { QueryPerformanceCounter :: proc(lpPerformanceCount: ^LARGE_INTEGER) -> BOOL --- GetExitCodeProcess :: proc(hProcess: HANDLE, lpExitCode: LPDWORD) -> BOOL --- TerminateProcess :: proc(hProcess: HANDLE, uExitCode: UINT) -> BOOL --- + OpenProcess :: proc(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) -> HANDLE --- + OpenThread :: proc(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwThreadId: DWORD) -> HANDLE --- + GetThreadContext :: proc( + hThread: HANDLE, + lpContext: LPCONTEXT, + ) -> BOOL --- CreateProcessW :: proc( lpApplicationName: LPCWSTR, lpCommandLine: LPWSTR, @@ -412,6 +444,7 @@ foreign kernel32 { LoadLibraryW :: proc(c_str: LPCWSTR) -> HMODULE --- FreeLibrary :: proc(h: HMODULE) -> BOOL --- GetProcAddress :: proc(h: HMODULE, c_str: LPCSTR) -> rawptr --- + LoadLibraryExW :: proc(c_str: LPCWSTR, file: HANDLE, flags: LoadLibraryEx_Flags) -> HMODULE --- GetFullPathNameW :: proc(filename: LPCWSTR, buffer_length: DWORD, buffer: LPCWSTR, file_part: ^LPCWSTR) -> DWORD --- @@ -429,6 +462,8 @@ foreign kernel32 { DisconnectNamedPipe :: proc(hNamedPipe: HANDLE) -> BOOL --- WaitNamedPipeW :: proc(lpNamedPipeName: LPCWSTR, nTimeOut: DWORD) -> BOOL --- + AllocConsole :: proc() -> BOOL --- + AttachConsole :: proc(dwProcessId: DWORD) -> BOOL --- SetConsoleCtrlHandler :: proc(HandlerRoutine: PHANDLER_ROUTINE, Add: BOOL) -> BOOL --- GenerateConsoleCtrlEvent :: proc(dwCtrlEvent: DWORD, dwProcessGroupId: DWORD) -> BOOL --- FreeConsole :: proc() -> BOOL --- @@ -451,15 +486,19 @@ foreign kernel32 { // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setfilecompletionnotificationmodes) SetFileCompletionNotificationModes :: proc(FileHandle: HANDLE, Flags: u8) -> BOOL --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-createiocompletionport) - CreateIoCompletionPort :: proc(FileHandle: HANDLE, ExistingCompletionPort: HANDLE, CompletionKey: ^uintptr, NumberOfConcurrentThreads: DWORD) -> HANDLE --- + CreateIoCompletionPort :: proc(FileHandle: HANDLE, ExistingCompletionPort: HANDLE, CompletionKey: ULONG_PTR, NumberOfConcurrentThreads: DWORD) -> HANDLE --- //[MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatus) - GetQueuedCompletionStatus :: proc(CompletionPort: HANDLE, lpNumberOfBytesTransferred: ^DWORD, lpCompletionKey: uintptr, lpOverlapped: ^^OVERLAPPED, dwMilliseconds: DWORD) -> BOOL --- + GetQueuedCompletionStatus :: proc(CompletionPort: HANDLE, lpNumberOfBytesTransferred: ^DWORD, lpCompletionKey: PULONG_PTR, lpOverlapped: ^^OVERLAPPED, dwMilliseconds: DWORD) -> BOOL --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatusex) GetQueuedCompletionStatusEx :: proc(CompletionPort: HANDLE, lpCompletionPortEntries: ^OVERLAPPED_ENTRY, ulCount: c_ulong, ulNumEntriesRemoved: ^c_ulong, dwMilliseconds: DWORD, fAlertable: BOOL) -> BOOL --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-postqueuedcompletionstatus) PostQueuedCompletionStatus :: proc(CompletionPort: HANDLE, dwNumberOfBytesTransferred: DWORD, dwCompletionKey: c_ulong, lpOverlapped: ^OVERLAPPED) -> BOOL --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-gethandleinformation) GetHandleInformation :: proc(hObject: HANDLE, lpdwFlags: ^DWORD) -> BOOL --- + + RtlCaptureStackBackTrace :: proc(FramesToSkip: ULONG, FramesToCapture: ULONG, BackTrace: [^]PVOID, BackTraceHash: PULONG) -> USHORT --- + + GetSystemPowerStatus :: proc(lpSystemPowerStatus: ^SYSTEM_POWER_STATUS) -> BOOL --- } DEBUG_PROCESS :: 0x00000001 @@ -510,6 +549,45 @@ THREAD_PRIORITY_IDLE :: THREAD_BASE_PRIORITY_IDLE THREAD_MODE_BACKGROUND_BEGIN :: 0x00010000 THREAD_MODE_BACKGROUND_END :: 0x00020000 +PROCESS_ALL_ACCESS :: 0x000F0000 | SYNCHRONIZE | 0xFFFF +PROCESS_CREATE_PROCESS :: 0x0080 +PROCESS_CREATE_THREAD :: 0x0002 +PROCESS_DUP_HANDLE :: 0x0040 +PROCESS_QUERY_INFORMATION :: 0x0400 +PROCESS_QUERY_LIMITED_INFORMATION :: 0x1000 +PROCESS_SET_INFORMATION :: 0x0200 +PROCESS_SET_QUOTA :: 0x0100 +PROCESS_SUSPEND_RESUME :: 0x0800 +PROCESS_TERMINATE :: 0x0001 +PROCESS_VM_OPERATION :: 0x0008 +PROCESS_VM_READ :: 0x0010 +PROCESS_VM_WRITE :: 0x0020 + +THREAD_ALL_ACCESS :: \ + THREAD_DIRECT_IMPERSONATION | + THREAD_GET_CONTEXT | + THREAD_IMPERSONATE | + THREAD_QUERY_INFORMATION | + THREAD_QUERY_LIMITED_INFORMATION | + THREAD_SET_CONTEXT | + THREAD_SET_INFORMATION | + THREAD_SET_LIMITED_INFORMATION | + THREAD_SET_THREAD_TOKEN | + THREAD_SUSPEND_RESUME | + THREAD_TERMINATE | + SYNCHRONIZE +THREAD_DIRECT_IMPERSONATION :: 0x0200 +THREAD_GET_CONTEXT :: 0x0008 +THREAD_IMPERSONATE :: 0x0100 +THREAD_QUERY_INFORMATION :: 0x0040 +THREAD_QUERY_LIMITED_INFORMATION :: 0x0800 +THREAD_SET_CONTEXT :: 0x0010 +THREAD_SET_INFORMATION :: 0x0020 +THREAD_SET_LIMITED_INFORMATION :: 0x0400 +THREAD_SET_THREAD_TOKEN :: 0x0080 +THREAD_SUSPEND_RESUME :: 0x0002 +THREAD_TERMINATE :: 0x0001 + COPY_FILE_FAIL_IF_EXISTS :: 0x00000001 COPY_FILE_RESTARTABLE :: 0x00000002 COPY_FILE_OPEN_SOURCE_FOR_WRITE :: 0x00000004 @@ -1005,150 +1083,8 @@ foreign kernel32 { HandlerRoutine :: proc "system" (dwCtrlType: DWORD) -> BOOL PHANDLER_ROUTINE :: HandlerRoutine - - - -DCB_Config :: struct { - fParity: bool, - fOutxCtsFlow: bool, - fOutxDsrFlow: bool, - fDtrControl: DTR_Control, - fDsrSensitivity: bool, - fTXContinueOnXoff: bool, - fOutX: bool, - fInX: bool, - fErrorChar: bool, - fNull: bool, - fRtsControl: RTS_Control, - fAbortOnError: bool, - BaudRate: DWORD, - ByteSize: BYTE, - Parity: Parity, - StopBits: Stop_Bits, - XonChar: byte, - XoffChar: byte, - ErrorChar: byte, - EvtChar: byte, -} -DTR_Control :: enum byte { - Disable = 0, - Enable = 1, - Handshake = 2, -} -RTS_Control :: enum byte { - Disable = 0, - Enable = 1, - Handshake = 2, - Toggle = 3, -} -Parity :: enum byte { - None = 0, - Odd = 1, - Even = 2, - Mark = 3, - Space = 4, -} -Stop_Bits :: enum byte { - One = 0, - One_And_A_Half = 1, - Two = 2, -} - -// A helper procedure to set the values of a DCB structure. -init_dcb_with_config :: proc "contextless" (dcb: ^DCB, config: DCB_Config) { - out: u32 - - // NOTE(tetra, 2022-09-21): On both Clang 14 on Windows, and MSVC, the bits in the bitfield - // appear to be defined from LSB to MSB order. - // i.e: `fBinary` (the first bitfield in the C source) is the LSB in the `settings` u32. - - out |= u32(1) << 0 // fBinary must always be true on Windows. - - out |= u32(config.fParity) << 1 - out |= u32(config.fOutxCtsFlow) << 2 - out |= u32(config.fOutxDsrFlow) << 3 - - out |= u32(config.fDtrControl) << 4 - - out |= u32(config.fDsrSensitivity) << 6 - out |= u32(config.fTXContinueOnXoff) << 7 - out |= u32(config.fOutX) << 8 - out |= u32(config.fInX) << 9 - out |= u32(config.fErrorChar) << 10 - out |= u32(config.fNull) << 11 - - out |= u32(config.fRtsControl) << 12 - - out |= u32(config.fAbortOnError) << 14 - - dcb.settings = out - - dcb.BaudRate = config.BaudRate - dcb.ByteSize = config.ByteSize - dcb.Parity = config.Parity - dcb.StopBits = config.StopBits - dcb.XonChar = config.XonChar - dcb.XoffChar = config.XoffChar - dcb.ErrorChar = config.ErrorChar - dcb.EvtChar = config.EvtChar - - dcb.DCBlength = size_of(DCB) -} -get_dcb_config :: proc "contextless" (dcb: DCB) -> (config: DCB_Config) { - config.fParity = bool((dcb.settings >> 1) & 0x01) - config.fOutxCtsFlow = bool((dcb.settings >> 2) & 0x01) - config.fOutxDsrFlow = bool((dcb.settings >> 3) & 0x01) - - config.fDtrControl = DTR_Control((dcb.settings >> 4) & 0x02) - - config.fDsrSensitivity = bool((dcb.settings >> 6) & 0x01) - config.fTXContinueOnXoff = bool((dcb.settings >> 7) & 0x01) - config.fOutX = bool((dcb.settings >> 8) & 0x01) - config.fInX = bool((dcb.settings >> 9) & 0x01) - config.fErrorChar = bool((dcb.settings >> 10) & 0x01) - config.fNull = bool((dcb.settings >> 11) & 0x01) - - config.fRtsControl = RTS_Control((dcb.settings >> 12) & 0x02) - - config.fAbortOnError = bool((dcb.settings >> 14) & 0x01) - - config.BaudRate = dcb.BaudRate - config.ByteSize = dcb.ByteSize - config.Parity = dcb.Parity - config.StopBits = dcb.StopBits - config.XonChar = dcb.XonChar - config.XoffChar = dcb.XoffChar - config.ErrorChar = dcb.ErrorChar - config.EvtChar = dcb.EvtChar - - return -} - -// NOTE(tetra): See get_dcb_config() and init_dcb_with_config() for help with initializing this. -DCB :: struct { - DCBlength: DWORD, // NOTE(tetra): Must be set to size_of(DCB). - BaudRate: DWORD, - settings: u32, // NOTE(tetra): These are bitfields in the C struct. - wReserved: WORD, - XOnLim: WORD, - XOffLim: WORD, - ByteSize: BYTE, - Parity: Parity, - StopBits: Stop_Bits, - XonChar: byte, - XoffChar: byte, - ErrorChar: byte, - EofChar: byte, - EvtChar: byte, - wReserved1: WORD, -} - -@(default_calling_convention="system") -foreign kernel32 { - GetCommState :: proc(handle: HANDLE, dcb: ^DCB) -> BOOL --- - SetCommState :: proc(handle: HANDLE, dcb: ^DCB) -> BOOL --- -} - +// NOTE(Jeroen, 2024-06-13): As Odin now supports bit_fields, we no longer need +// a helper procedure. `init_dcb_with_config` and `get_dcb_config` have been removed. LPFIBER_START_ROUTINE :: #type proc "system" (lpFiberParameter: LPVOID) @@ -1206,6 +1142,30 @@ SYSTEM_LOGICAL_PROCESSOR_INFORMATION :: struct { DummyUnion: DUMMYUNIONNAME_u, } +SYSTEM_POWER_STATUS :: struct { + ACLineStatus: AC_Line_Status, + BatteryFlag: Battery_Flags, + BatteryLifePercent: BYTE, + SystemStatusFlag: BYTE, + BatteryLifeTime: DWORD, + BatteryFullLifeTime: DWORD, +} + +AC_Line_Status :: enum BYTE { + Offline = 0, + Online = 1, + Unknown = 255, +} + +Battery_Flag :: enum BYTE { + High = 0, + Low = 1, + Critical = 2, + Charging = 3, + No_Battery = 7, +} +Battery_Flags :: bit_set[Battery_Flag; BYTE] + /* Global Memory Flags */ GMEM_FIXED :: 0x0000 GMEM_MOVEABLE :: 0x0002 @@ -1224,3 +1184,5 @@ GMEM_INVALID_HANDLE :: 0x8000 GHND :: (GMEM_MOVEABLE | GMEM_ZEROINIT) GPTR :: (GMEM_FIXED | GMEM_ZEROINIT) + +LPTOP_LEVEL_EXCEPTION_FILTER :: PVECTORED_EXCEPTION_HANDLER diff --git a/core/sys/windows/ntdll.odin b/core/sys/windows/ntdll.odin index 56c24f1a2..b75ac695a 100644 --- a/core/sys/windows/ntdll.odin +++ b/core/sys/windows/ntdll.odin @@ -6,4 +6,110 @@ foreign import ntdll_lib "system:ntdll.lib" @(default_calling_convention="system") foreign ntdll_lib { RtlGetVersion :: proc(lpVersionInformation: ^OSVERSIONINFOEXW) -> NTSTATUS --- + + + NtQueryInformationProcess :: proc( + ProcessHandle: HANDLE, + ProcessInformationClass: PROCESS_INFO_CLASS, + ProcessInformation: rawptr, + ProcessInformationLength: u32, + ReturnLength: ^u32, + ) -> u32 --- +} + + +PROCESS_INFO_CLASS :: enum i32 { + ProcessBasicInformation = 0, + ProcessDebugPort = 7, + ProcessWow64Information = 26, + ProcessImageFileName = 27, + ProcessBreakOnTermination = 29, + ProcessTelemetryIdInformation = 64, + ProcessSubsystemInformation = 75, +} + + + +PROCESS_BASIC_INFORMATION :: struct { + ExitStatus: NTSTATUS, + PebBaseAddress: ^PEB, + AffinityMask: ULONG_PTR, + BasePriority: KPRIORITY, + UniqueProcessId: ULONG_PTR, + InheritedFromUniqueProcessId: ULONG_PTR, +} + +KPRIORITY :: rawptr + +PPS_POST_PROCESS_INIT_ROUTINE :: proc "system" () + + +PEB :: struct { + _: [2]u8, + BeingDebugged: u8, + _: [1]u8, + _: [2]rawptr, + Ldr: ^PEB_LDR_DATA, + ProcessParameters: ^RTL_USER_PROCESS_PARAMETERS, + _: [104]u8, + _: [52]rawptr, + PostProcessInitRoutine: PPS_POST_PROCESS_INIT_ROUTINE, + _: [128]u8, + _: [1]rawptr, + SessionId: u32, +} + + + + +PEB_LDR_DATA :: struct { + _: [8]u8, + _: [3]rawptr, + InMemoryOrderModuleList: LIST_ENTRY, +} + +RTL_USER_PROCESS_PARAMETERS :: struct { + MaximumLength: u32, + Length: u32, + Flags: u32, + DebugFlags: u32, + ConsoleHandle: rawptr, + ConsoleFlags: u32, + StdInputHandle: rawptr, + StdOutputHandle: rawptr, + StdErrorHandle: rawptr, + CurrentDirectoryPath: UNICODE_STRING, + CurrentDirectoryHandle: rawptr, + DllPath: UNICODE_STRING, + ImagePathName: UNICODE_STRING, + CommandLine: UNICODE_STRING, + Environment: rawptr, + StartingPositionLeft: u32, + StartingPositionTop: u32, + Width: u32, + Height: u32, + CharWidth: u32, + CharHeight: u32, + ConsoleTextAttributes: u32, + WindowFlags: u32, + ShowWindowFlags: u32, + WindowTitle: UNICODE_STRING, + DesktopName: UNICODE_STRING, + ShellInfo: UNICODE_STRING, + RuntimeData: UNICODE_STRING, + DLCurrentDirectory: [32]RTL_DRIVE_LETTER_CURDIR, + EnvironmentSize: u32, +} + +RTL_DRIVE_LETTER_CURDIR :: struct { + Flags: u16, + Length: u16, + TimeStamp: u32, + DosPath: UNICODE_STRING, +} + + +LIST_ENTRY :: struct { + Flink: ^LIST_ENTRY, + Blink: ^LIST_ENTRY, } \ No newline at end of file diff --git a/core/sys/windows/ole32.odin b/core/sys/windows/ole32.odin index d344db5f0..3ff86b9fc 100644 --- a/core/sys/windows/ole32.odin +++ b/core/sys/windows/ole32.odin @@ -7,13 +7,13 @@ foreign import "system:Ole32.lib" /* typedef enum tagCOINIT { - COINIT_APARTMENTTHREADED = 0x2, // Apartment model + COINIT_APARTMENTTHREADED = 0x2, // Apartment model #if (_WIN32_WINNT >= 0x0400 ) || defined(_WIN32_DCOM) // DCOM - // These constants are only valid on Windows NT 4.0 - COINIT_MULTITHREADED = COINITBASE_MULTITHREADED, - COINIT_DISABLE_OLE1DDE = 0x4, // Don't use DDE for Ole1 support. - COINIT_SPEED_OVER_MEMORY = 0x8, // Trade memory for speed. + // These constants are only valid on Windows NT 4.0 + COINIT_MULTITHREADED = COINITBASE_MULTITHREADED, + COINIT_DISABLE_OLE1DDE = 0x4, // Don't use DDE for Ole1 support. + COINIT_SPEED_OVER_MEMORY = 0x8, // Trade memory for speed. #endif // DCOM } COINIT; */ @@ -26,9 +26,11 @@ COINIT :: enum DWORD { } IUnknown :: struct { - using Vtbl: ^IUnknownVtbl, + using _iunknown_vtable: ^IUnknown_VTable, } -IUnknownVtbl :: struct { + +IUnknownVtbl :: IUnknown_VTable +IUnknown_VTable :: struct { QueryInterface: proc "system" (This: ^IUnknown, riid: REFIID, ppvObject: ^rawptr) -> HRESULT, AddRef: proc "system" (This: ^IUnknown) -> ULONG, Release: proc "system" (This: ^IUnknown) -> ULONG, diff --git a/core/sys/windows/shell32.odin b/core/sys/windows/shell32.odin index 4108d54d8..6831f4339 100644 --- a/core/sys/windows/shell32.odin +++ b/core/sys/windows/shell32.odin @@ -5,7 +5,7 @@ foreign import shell32 "system:Shell32.lib" @(default_calling_convention="system") foreign shell32 { - CommandLineToArgvW :: proc(cmd_list: wstring, num_args: ^c_int) -> ^wstring --- + CommandLineToArgvW :: proc(cmd_list: wstring, num_args: ^c_int) -> [^]wstring --- ShellExecuteW :: proc( hwnd: HWND, lpOperation: LPCWSTR, @@ -30,6 +30,11 @@ foreign shell32 { SHGetKnownFolderIDList :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, ppidl: rawptr) -> HRESULT --- SHSetKnownFolderPath :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, pszPath: PCWSTR ) -> HRESULT --- SHGetKnownFolderPath :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, ppszPath: ^LPWSTR) -> HRESULT --- + + DragAcceptFiles :: proc(hWnd: HWND, fAccept: BOOL) --- + DragQueryPoint :: proc(hDrop: HDROP, ppt: ^POINT) -> BOOL --- + DragQueryFileW :: proc(hDrop: HDROP, iFile: UINT, lpszFile: LPWSTR, cch: UINT) -> UINT --- + DragFinish :: proc(hDrop: HDROP) --- // @New } APPBARDATA :: struct { @@ -67,6 +72,8 @@ ABE_BOTTOM :: 3 KNOWNFOLDERID :: GUID REFKNOWNFOLDERID :: ^KNOWNFOLDERID +HDROP :: HANDLE + KNOWN_FOLDER_FLAG :: enum u32 { DEFAULT = 0x00000000, diff --git a/core/sys/windows/tlhelp.odin b/core/sys/windows/tlhelp.odin new file mode 100644 index 000000000..45d5a3ff9 --- /dev/null +++ b/core/sys/windows/tlhelp.odin @@ -0,0 +1,101 @@ +//+build windows +package sys_windows + +foreign import kernel32 "system:Kernel32.lib" + +@(default_calling_convention="system") +foreign kernel32 { + CreateToolhelp32Snapshot :: proc (dwFlags: DWORD, th32ProcessID: DWORD) -> HANDLE --- + Process32FirstW :: proc (hSnapshot: HANDLE, lppe: LPPROCESSENTRY32W) -> BOOL --- + Process32NextW :: proc (hSnapshot: HANDLE, lppe: LPPROCESSENTRY32W) -> BOOL --- + Thread32First :: proc (hSnapshot: HANDLE, lpte: LPTHREADENTRY32) -> BOOL --- + Thread32Next :: proc (hSnapshot: HANDLE, lpte: LPTHREADENTRY32) -> BOOL --- + Module32FirstW :: proc (hSnapshot: HANDLE, lpme: LPMODULEENTRY32W) -> BOOL --- + Module32NextW :: proc (hSnapshot: HANDLE, lpme: LPMODULEENTRY32W) -> BOOL --- + Heap32ListFirst :: proc (hSnapshot: HANDLE, lphl: LPHEAPLIST32) -> BOOL --- + Heap32ListNext :: proc (hSnapshot: HANDLE, lphl: LPHEAPLIST32) -> BOOL --- + Heap32First :: proc (lphe: LPHEAPENTRY32, th32ProcessID: DWORD, th32HeapID: ULONG_PTR) -> BOOL --- + Heap32Next :: proc (lphe: LPHEAPENTRY32) -> BOOL --- + Toolhelp32ReadProcessMemory :: proc ( + th32ProcessID: DWORD, + lpBaseAddress: LPCVOID, + lpBuffer: LPVOID, + cbRead: SIZE_T, + lpNumberOfBytesRead: ^SIZE_T, + ) -> BOOL --- +} + +MAX_MODULE_NAME32 :: 255 + +TH32CS_INHERIT :: 0x80000000 +TH32CS_SNAPHEAPLIST :: 0x00000001 +TH32CS_SNAPPROCESS :: 0x00000002 +TH32CS_SNAPTHREAD :: 0x00000004 +TH32CS_SNAPMODULE :: 0x00000008 +TH32CS_SNAPMODULE32 :: 0x00000010 +TH32CS_SNAPALL :: TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE + +PROCESSENTRY32W :: struct { + dwSize: DWORD, + cntUsage: DWORD, + th32ProcessID: DWORD, + th32DefaultHeapID: ULONG_PTR, + th32ModuleID: DWORD, + cntThreads: DWORD, + th32ParentProcessID: DWORD, + pcPriClassBase: LONG, + dwFlags: DWORD, + szExeFile: [MAX_PATH]WCHAR, +} + +LPPROCESSENTRY32W :: ^PROCESSENTRY32W + +THREADENTRY32 :: struct { + dwSize: DWORD, + cntUsage: DWORD, + th32ThreadID: DWORD, + th32OwnerProcessID: DWORD, + tpBasePri: LONG, + tpDeltaPri: LONG, + dwFlags: DWORD, +} + +LPTHREADENTRY32 :: ^THREADENTRY32 + +MODULEENTRY32W :: struct { + dwSize: DWORD, + th32ModuleID: DWORD, + th32ProcessID: DWORD, + GlblcntUsage: DWORD, + ProccntUsage: DWORD, + modBaseAddr: ^BYTE, + modBaseSize: DWORD, + hModule: HMODULE, + szModule: [MAX_MODULE_NAME32 + 1]WCHAR, + szExePath: [MAX_PATH]WCHAR, +} + +LPMODULEENTRY32W :: ^MODULEENTRY32W + +HEAPLIST32 :: struct { + dwSize: SIZE_T, + th32ProcessID: DWORD, + th32HeapID: ULONG_PTR, + dwFlags: DWORD, +} + +LPHEAPLIST32 :: ^HEAPLIST32 + +HEAPENTRY32 :: struct { + dwSize: SIZE_T, + hHandle: HANDLE, + dwAddress: ULONG_PTR, + dwBlockSize: SIZE_T, + dwFlags: DWORD, + dwLockCount: DWORD, + dwResvd: DWORD, + th32ProcessID: DWORD, + th32HeapID: ULONG_PTR, +} + +LPHEAPENTRY32 :: ^HEAPENTRY32 diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 4b54f0ed1..35f4174eb 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -34,6 +34,7 @@ HGDIOBJ :: distinct HANDLE HBITMAP :: distinct HANDLE HGLOBAL :: distinct HANDLE HHOOK :: distinct HANDLE +HWINEVENTHOOK :: distinct HANDLE HKEY :: distinct HANDLE HDESK :: distinct HANDLE HFONT :: distinct HANDLE @@ -63,6 +64,7 @@ LONG_PTR :: int UINT_PTR :: uintptr ULONG :: c_ulong ULONGLONG :: c_ulonglong +LONGLONG :: c_longlong UCHAR :: BYTE NTSTATUS :: c.long COLORREF :: DWORD @@ -93,10 +95,14 @@ LONG32 :: i32 ULONG64 :: u64 LONG64 :: i64 +DWORD64 :: u64 +PDWORD64 :: ^DWORD64 + PDWORD_PTR :: ^DWORD_PTR ATOM :: distinct WORD wstring :: [^]WCHAR +PWSTR :: [^]WCHAR PBYTE :: ^BYTE LPBYTE :: ^BYTE @@ -699,6 +705,14 @@ WNDPROC :: #type proc "system" (HWND, UINT, WPARAM, LPARAM) -> LRESULT HOOKPROC :: #type proc "system" (code: c_int, wParam: WPARAM, lParam: LPARAM) -> LRESULT +WINEVENTPROC :: #type proc "system" ( + hWinEventHook: HWINEVENTHOOK, + event: DWORD, + hwnd: HWND, + idObject, idChild: LONG, + idEventThread, dwmsEventTime: DWORD, +) + CWPRETSTRUCT :: struct { lResult: LRESULT, lParam: LPARAM, @@ -2132,6 +2146,7 @@ SECURITY_IMPERSONATION_LEVEL :: enum { SECURITY_INFORMATION :: DWORD ANYSIZE_ARRAY :: 1 +PLUID_AND_ATTRIBUTES :: ^LUID_AND_ATTRIBUTES LUID_AND_ATTRIBUTES :: struct { Luid: LUID, Attributes: DWORD, @@ -2480,9 +2495,9 @@ OBJECT_ATTRIBUTES :: struct { } UNICODE_STRING :: struct { - Length: u16, - MaximumLength: u16, - Buffer: ^u16, + Length: u16 `fmt:"-"`, + MaximumLength: u16 `fmt:"-"`, + Buffer: [^]u16 `fmt:"s,Length"`, } OVERLAPPED :: struct { @@ -2557,7 +2572,139 @@ EXCEPTION_RECORD :: struct { ExceptionInformation: [EXCEPTION_MAXIMUM_PARAMETERS]LPVOID, } -CONTEXT :: struct{} // TODO(bill) + +CONTEXT :: struct { + P1Home: DWORD64, + P2Home: DWORD64, + P3Home: DWORD64, + P4Home: DWORD64, + P5Home: DWORD64, + P6Home: DWORD64, + ContextFlags: DWORD, + MxCsr: DWORD, + SegCs: WORD, + SegDs: WORD, + SegEs: WORD, + SegFs: WORD, + SegGs: WORD, + SegSs: WORD, + EFlags: DWORD, + Dr0: DWORD64, + Dr1: DWORD64, + Dr2: DWORD64, + Dr3: DWORD64, + Dr6: DWORD64, + Dr7: DWORD64, + Rax: DWORD64, + Rcx: DWORD64, + Rdx: DWORD64, + Rbx: DWORD64, + Rsp: DWORD64, + Rbp: DWORD64, + Rsi: DWORD64, + Rdi: DWORD64, + R8: DWORD64, + R9: DWORD64, + R10: DWORD64, + R11: DWORD64, + R12: DWORD64, + R13: DWORD64, + R14: DWORD64, + R15: DWORD64, + Rip: DWORD64, + _: struct #raw_union { + FltSave: XMM_SAVE_AREA32, + Q: [16]NEON128, + D: [32]ULONGLONG, + _: struct { + Header: [2]M128A, + Legacy: [8]M128A, + Xmm0: M128A, + Xmm1: M128A, + Xmm2: M128A, + Xmm3: M128A, + Xmm4: M128A, + Xmm5: M128A, + Xmm6: M128A, + Xmm7: M128A, + Xmm8: M128A, + Xmm9: M128A, + Xmm10: M128A, + Xmm11: M128A, + Xmm12: M128A, + Xmm13: M128A, + Xmm14: M128A, + Xmm15: M128A, + }, + S: [32]DWORD, + }, + VectorRegister: [26]M128A, + VectorControl: DWORD64, + DebugControl: DWORD64, + LastBranchToRip: DWORD64, + LastBranchFromRip: DWORD64, + LastExceptionToRip: DWORD64, + LastExceptionFromRip: DWORD64, +} + +PCONTEXT :: ^CONTEXT +LPCONTEXT :: ^CONTEXT + +when size_of(uintptr) == 32 { + XSAVE_FORMAT :: struct #align(16) { + ControlWord: WORD, + StatusWord: WORD, + TagWord: BYTE, + Reserved1: BYTE, + ErrorOpcode: WORD, + ErrorOffset: DWORD, + ErrorSelector: WORD, + Reserved2: WORD, + DataOffset: DWORD, + DataSelector: WORD, + Reserved3: WORD, + MxCsr: DWORD, + MxCsr_Mask: DWORD, + FloatRegisters: [8]M128A, + // 32-bit specific + XmmRegisters: [8]M128A, + Reserved4: [192]BYTE, + StackControl: [7]DWORD, + Cr0NpxState: DWORD, + } +} else { + XSAVE_FORMAT :: struct #align(16) { + ControlWord: WORD, + StatusWord: WORD, + TagWord: BYTE, + Reserved1: BYTE, + ErrorOpcode: WORD, + ErrorOffset: DWORD, + ErrorSelector: WORD, + Reserved2: WORD, + DataOffset: DWORD, + DataSelector: WORD, + Reserved3: WORD, + MxCsr: DWORD, + MxCsr_Mask: DWORD, + FloatRegisters: [8]M128A, + // 64-bit specific + XmmRegisters: [16]M128A, + Reserved4: [96]BYTE, + } +} + +XMM_SAVE_AREA32 :: XSAVE_FORMAT + +M128A :: struct { + Low: ULONGLONG, + High: LONGLONG, +} + +NEON128 :: struct { + Low: ULONGLONG, + High: LONGLONG, +} EXCEPTION_POINTERS :: struct { ExceptionRecord: ^EXCEPTION_RECORD, @@ -2661,6 +2808,22 @@ OSVERSIONINFOEXW :: struct { wReserved: UCHAR, } +LoadLibraryEx_Flag :: enum DWORD { + LOAD_LIBRARY_AS_DATAFILE = 1, // 1 << 1: 0x0002, + LOAD_WITH_ALTERED_SEARCH_PATH = 3, // 1 << 3: 0x0008, + LOAD_IGNORE_CODE_AUTHZ_LEVEL = 4, // 1 << 4: 0x0010, + LOAD_LIBRARY_AS_IMAGE_RESOURCE = 5, // 1 << 5: 0x0020, + LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 6, // 1 << 6: 0x0040, + LOAD_LIBRARY_REQUIRE_SIGNED_TARGET = 7, // 1 << 7: 0x0080, + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 8, // 1 << 8: 0x0100, + LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 9, // 1 << 9: 0x0200, + LOAD_LIBRARY_SEARCH_USER_DIRS = 10, // 1 << 10: 0x0400, + LOAD_LIBRARY_SEARCH_SYSTEM32 = 11, // 1 << 11: 0x0800, + LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 12, // 1 << 12: 0x1000, + LOAD_LIBRARY_SAFE_CURRENT_DIRS = 13, // 1 << 13: 0x2000, +} +LoadLibraryEx_Flags :: distinct bit_set[LoadLibraryEx_Flag] + // https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-quota_limits // Used in LogonUserExW PQUOTA_LIMITS :: struct { @@ -2704,23 +2867,6 @@ PROFILEINFOW :: struct { hProfile: HANDLE, } -// Used in LookupAccountNameW -SID_NAME_USE :: distinct DWORD - -SID_TYPE :: enum SID_NAME_USE { - User = 1, - Group, - Domain, - Alias, - WellKnownGroup, - DeletedAccount, - Invalid, - Unknown, - Computer, - Label, - LogonSession, -} - SECURITY_MAX_SID_SIZE :: 68 // https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid @@ -3970,6 +4116,70 @@ CONSOLE_CURSOR_INFO :: struct { PCONSOLE_SCREEN_BUFFER_INFO :: ^CONSOLE_SCREEN_BUFFER_INFO PCONSOLE_CURSOR_INFO :: ^CONSOLE_CURSOR_INFO +Event_Type :: enum WORD { + KEY_EVENT = 0x0001, + MOUSE_EVENT = 0x0002, + WINDOW_BUFFER_SIZE_EVENT = 0x0004, + MENU_EVENT = 0x0008, + FOCUS_EVENT = 0x0010, +} + +INPUT_RECORD :: struct { + EventType: Event_Type, + Event: struct #raw_union { + KeyEvent: KEY_EVENT_RECORD, + MouseEvent: MOUSE_EVENT_RECORD, + WindowBufferSizeEvent: WINDOW_BUFFER_SIZE_RECORD, + MenuEvent: MENU_EVENT_RECORD, + FocusEvent: FOCUS_EVENT_RECORD, + }, +} + +Control_Key_State_Bits :: enum { + RIGHT_ALT_PRESSED, + LEFT_ALT_PRESSED, + RIGHT_CTRL_PRESSED, + LEFT_CTRL_PRESSED, + SHIFT_PRESSED, + NUMLOCK_ON, + SCROLLLOCK_ON, + CAPSLOCK_ON, + ENHANCED_KEY, +} +Control_Key_State :: bit_set[Control_Key_State_Bits; DWORD] + +KEY_EVENT_RECORD :: struct { + bKeyDown: BOOL, + wRepeatCount: WORD, + wVirtualKeyCode: WORD, + wVirtualScanCode: WORD, + uChar: struct #raw_union { + UnicodeChar: WCHAR, + AsciiChar: CHAR, + }, + dwControlKeyState: Control_Key_State, +} + +MOUSE_EVENT_RECORD :: struct { + dwMousePosition: COORD, + dwButtonState: DWORD, + dwControlKeyState: DWORD, + dwEventFlags: DWORD, +} + +WINDOW_BUFFER_SIZE_RECORD :: struct { + dwSize: COORD, +} + +MENU_EVENT_RECORD :: struct { + dwCommandId: UINT, +} + +FOCUS_EVENT_RECORD :: struct { + bSetFocus: BOOL, +} + + // // Networking // @@ -4204,3 +4414,92 @@ SOCKADDR :: struct { sa_family: ADDRESS_FAMILY, sa_data: [14]CHAR, } + +DTR_Control :: enum byte { + Disable = 0, + Enable = 1, + Handshake = 2, +} +RTS_Control :: enum byte { + Disable = 0, + Enable = 1, + Handshake = 2, + Toggle = 3, +} +Parity :: enum byte { + None = 0, + Odd = 1, + Even = 2, + Mark = 3, + Space = 4, +} +Stop_Bits :: enum byte { + One = 0, + One_And_A_Half = 1, + Two = 2, +} + +DCB :: struct { + DCBlength: DWORD, + BaudRate: DWORD, + using _: bit_field DWORD { + fBinary: bool | 1, + fParity: bool | 1, + fOutxCtsFlow: bool | 1, + fOutxDsrFlow: bool | 1, + fDtrControl: DTR_Control | 2, + fDsrSensitivity: bool | 1, + fTXContinueOnXoff: bool | 1, + fOutX: bool | 1, + fInX: bool | 1, + fErrorChar: bool | 1, + fNull: bool | 1, + fRtsControl: RTS_Control | 2, + fAbortOnError: bool | 1, + }, + wReserved: WORD, + XOnLim: WORD, + XOffLim: WORD, + ByteSize: BYTE, + Parity: Parity, + StopBits: Stop_Bits, + XonChar: byte, + XoffChar: byte, + ErrorChar: byte, + EofChar: byte, + EvtChar: byte, + wReserved1: WORD, +} + +COMMTIMEOUTS :: struct { + ReadIntervalTimeout: DWORD, + ReadTotalTimeoutMultiplier: DWORD, + ReadTotalTimeoutConstant: DWORD, + WriteTotalTimeoutMultiplier: DWORD, + WriteTotalTimeoutConstant: DWORD, +} + +Com_Stat_Bits :: enum { + fCtsHold, + fDsrHold, + fRlsdHold, + fXoffHold, + fXoffSent, + fEof, + fTxim, +} +COMSTAT :: struct { + bits: bit_set[Com_Stat_Bits; DWORD], + cbInQue: DWORD, + cbOutQue: DWORD, +} + +Com_Error_Bits :: enum { + RXOVER, + OVERRUN, + RXPARITY, + FRAME, + BREAK, +} +Com_Error :: bit_set[Com_Error_Bits; DWORD] + diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index a589c3ec9..3bc083acb 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -17,6 +17,18 @@ foreign user32 { GetClassNameW :: proc(hWnd: HWND, lpClassName: LPWSTR, nMaxCount: c_int) -> c_int --- + GetParent :: proc(hWnd: HWND) -> HWND --- + IsWindowVisible :: proc(hWnd: HWND) -> BOOL --- + SetWinEventHook :: proc( + eventMin, eventMax: DWORD, + hmodWinEventProc: HMODULE, + pfnWinEvenProc: WINEVENTPROC, + idProcess, idThread: DWORD, + dwFlags: WinEventFlags, + ) -> HWINEVENTHOOK --- + + IsChild :: proc(hWndParent, hWnd: HWND) -> BOOL --- + RegisterClassW :: proc(lpWndClass: ^WNDCLASSW) -> ATOM --- RegisterClassExW :: proc(^WNDCLASSEXW) -> ATOM --- UnregisterClassW :: proc(lpClassName: LPCWSTR, hInstance: HINSTANCE) -> BOOL --- @@ -126,6 +138,7 @@ foreign user32 { CreatePopupMenu :: proc() -> HMENU --- DestroyMenu :: proc(hMenu: HMENU) -> BOOL --- AppendMenuW :: proc(hMenu: HMENU, uFlags: UINT, uIDNewItem: UINT_PTR, lpNewItem: LPCWSTR) -> BOOL --- + SetMenu :: proc(hWnd: HWND, hMenu: HMENU) -> BOOL --- TrackPopupMenu :: proc(hMenu: HMENU, uFlags: UINT, x: int, y: int, nReserved: int, hWnd: HWND, prcRect: ^RECT) -> i32 --- RegisterWindowMessageW :: proc(lpString: LPCWSTR) -> UINT --- @@ -355,9 +368,9 @@ RAWHID :: struct { RAWMOUSE :: struct { usFlags: USHORT, - DUMMYUNIONNAME: struct #raw_union { + using DUMMYUNIONNAME: struct #raw_union { ulButtons: ULONG, - DUMMYSTRUCTNAME: struct { + using DUMMYSTRUCTNAME: struct { usButtonFlags: USHORT, usButtonData: USHORT, }, @@ -433,7 +446,7 @@ RID_DEVICE_INFO_MOUSE :: struct { RID_DEVICE_INFO :: struct { cbSize: DWORD, dwType: DWORD, - DUMMYUNIONNAME: struct #raw_union { + using DUMMYUNIONNAME: struct #raw_union { mouse: RID_DEVICE_INFO_MOUSE, keyboard: RID_DEVICE_INFO_KEYBOARD, hid: RID_DEVICE_INFO_HID, @@ -454,10 +467,21 @@ RIDEV_DEVNOTIFY :: 0x00002000 RID_HEADER :: 0x10000005 RID_INPUT :: 0x10000003 +RIDI_PREPARSEDDATA :: 0x20000005 +RIDI_DEVICENAME :: 0x20000007 +RIDI_DEVICEINFO :: 0x2000000b + RIM_TYPEMOUSE :: 0 RIM_TYPEKEYBOARD :: 1 RIM_TYPEHID :: 2 +RI_KEY_MAKE :: 0 +RI_KEY_BREAK :: 1 +RI_KEY_E0 :: 2 +RI_KEY_E1 :: 4 +RI_KEY_TERMSRV_SET_LED :: 8 +RI_KEY_TERMSRV_SHADOW :: 0x10 + MOUSE_MOVE_RELATIVE :: 0x00 MOUSE_MOVE_ABSOLUTE :: 0x01 MOUSE_VIRTUAL_DESKTOP :: 0x02 @@ -483,19 +507,6 @@ RI_MOUSE_BUTTON_5_UP :: 0x0200 RI_MOUSE_WHEEL :: 0x0400 RI_MOUSE_HWHEEL :: 0x0800 -HID_USAGE_PAGE_GENERIC :: 0x01 -HID_USAGE_PAGE_GAME :: 0x05 -HID_USAGE_PAGE_LED :: 0x08 -HID_USAGE_PAGE_BUTTON :: 0x09 - -HID_USAGE_GENERIC_POINTER :: 0x01 -HID_USAGE_GENERIC_MOUSE :: 0x02 -HID_USAGE_GENERIC_JOYSTICK :: 0x04 -HID_USAGE_GENERIC_GAMEPAD :: 0x05 -HID_USAGE_GENERIC_KEYBOARD :: 0x06 -HID_USAGE_GENERIC_KEYPAD :: 0x07 -HID_USAGE_GENERIC_MULTI_AXIS_CONTROLLER :: 0x08 - WINDOWPLACEMENT :: struct { length: UINT, flags: UINT, @@ -520,11 +531,11 @@ WINDOWINFO :: struct { PWINDOWINFO :: ^WINDOWINFO DRAWTEXTPARAMS :: struct { - cbSize : UINT , - iTabLength: int , - iLeftMargin: int , - iRightMargin: int , - uiLengthDrawn: UINT , + cbSize: UINT, + iTabLength: int, + iLeftMargin: int, + iRightMargin: int, + uiLengthDrawn: UINT, } PDRAWTEXTPARAMS :: ^DRAWTEXTPARAMS @@ -569,3 +580,12 @@ RedrawWindowFlags :: enum UINT { RDW_FRAME = 0x0400, RDW_NOFRAME = 0x0800, } + +// OUTOFCONTEXT is the zero value, use {} +WinEventFlags :: bit_set[WinEventFlag; DWORD] + +WinEventFlag :: enum DWORD { + SKIPOWNTHREAD = 0, + SKIPOWNPROCESS = 1, + INCONTEXT = 2, +} diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index c68d58de0..64d623000 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -53,8 +53,8 @@ utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 { return text[:n] } utf8_to_wstring :: proc(s: string, allocator := context.temp_allocator) -> wstring { - if res := utf8_to_utf16(s, allocator); res != nil { - return &res[0] + if res := utf8_to_utf16(s, allocator); len(res) > 0 { + return raw_data(res) } return nil } @@ -202,7 +202,7 @@ get_computer_name_and_account_sid :: proc(username: string) -> (computer_name: s username_w := utf8_to_utf16(username, context.temp_allocator) cbsid: DWORD computer_name_size: DWORD - pe_use := SID_TYPE.User + pe_use := SID_NAME_USE.SidTypeUser res := LookupAccountNameW( nil, // Look on this computer first @@ -244,7 +244,7 @@ get_sid :: proc(username: string, sid: ^SID) -> (ok: bool) { username_w := utf8_to_utf16(username, context.temp_allocator) cbsid: DWORD computer_name_size: DWORD - pe_use := SID_TYPE.User + pe_use := SID_NAME_USE.SidTypeUser res := LookupAccountNameW( nil, // Look on this computer first diff --git a/core/sys/windows/winerror.odin b/core/sys/windows/winerror.odin index 118327ffa..c66a22322 100644 --- a/core/sys/windows/winerror.odin +++ b/core/sys/windows/winerror.odin @@ -47,7 +47,7 @@ ERROR_PIPE_BUSY : DWORD : 231 E_NOTIMPL :: HRESULT(-0x7fff_bfff) // 0x8000_4001 -SUCCEEDED :: #force_inline proc(#any_int result: int) -> bool { return result >= 0 } +SUCCEEDED :: #force_inline proc "contextless" (#any_int result: int) -> bool { return result >= 0 } System_Error :: enum DWORD { diff --git a/core/sys/windows/wow64_apiset.odin b/core/sys/windows/wow64_apiset.odin new file mode 100644 index 000000000..28558e9ca --- /dev/null +++ b/core/sys/windows/wow64_apiset.odin @@ -0,0 +1,85 @@ +//+build windows +package sys_windows + +foreign import kernel32 "system:Kernel32.lib" + +@(default_calling_convention="system") +foreign kernel32 { + GetSystemWow64Directory2W :: proc (lpBuffer: LPWSTR, uSize: UINT, ImageFileMachineTyp: WORD) -> UINT --- + GetSystemWow64DirectoryW :: proc (lpBuffer: LPWSTR, uSize: UINT) -> UINT --- + IsWow64GuestMachineSupported :: proc (WowGuestMachine: USHORT, MachineIsSupported: ^BOOL) -> HRESULT --- + IsWow64Process :: proc (hProcess: HANDLE, Wow64Process: PBOOL) -> BOOL --- + IsWow64Process2 :: proc (hProcess: HANDLE, pProcessMachine: ^USHORT, pNativeMachine: ^USHORT) -> BOOL --- + Wow64EnableWow64FsRedirection :: proc (Wow64FsEnableRedirection: BOOLEAN) -> BOOLEAN --- + Wow64DisableWow64FsRedirection :: proc (OldValue: ^PVOID) -> BOOL --- + Wow64RevertWow64FsRedirection :: proc (OlValue: PVOID) -> BOOL --- + Wow64GetThreadContext :: proc (hThread: HANDLE, lpContext: PWOW64_CONTEXT) -> BOOL --- + Wow64SetThreadContext :: proc(hThread: HANDLE, lpContext: ^WOW64_CONTEXT) -> BOOL --- + Wow64SetThreadDefaultGuestMachine :: proc(Machine: USHORT) -> USHORT --- + Wow64SuspendThread :: proc (hThread: HANDLE) -> DWORD --- +} + +WOW64_CONTEXT_i386 :: 0x00010000 + +WOW64_CONTEXT_CONTROL :: (WOW64_CONTEXT_i386 | 0x00000001) +WOW64_CONTEXT_INTEGER :: (WOW64_CONTEXT_i386 | 0x00000002) +WOW64_CONTEXT_SEGMENTS :: (WOW64_CONTEXT_i386 | 0x00000004) + +WOW64_CONTEXT_FLOATING_POINT :: (WOW64_CONTEXT_i386 | 0x00000008) +WOW64_CONTEXT_DEBUG_REGISTERS :: (WOW64_CONTEXT_i386 | 0x00000010) +WOW64_CONTEXT_EXTENDED_REGISTERS :: (WOW64_CONTEXT_i386 | 0x00000020) +WOW64_CONTEXT_FULL :: (WOW64_CONTEXT_CONTROL | WOW64_CONTEXT_INTEGER | WOW64_CONTEXT_SEGMENTS) +WOW64_CONTEXT_ALL :: ( + WOW64_CONTEXT_CONTROL | + WOW64_CONTEXT_INTEGER | + WOW64_CONTEXT_SEGMENTS | + WOW64_CONTEXT_FLOATING_POINT | + WOW64_CONTEXT_DEBUG_REGISTERS | + WOW64_CONTEXT_EXTENDED_REGISTERS) + +WOW64_SIZE_OF_80387_REGISTERS :: 80 +WOW64_MAXIMUM_SUPPORTED_EXTENSION :: 512 + +WOW64_CONTEXT :: struct { + ContextFlags: DWORD, + Dr0: DWORD, + Dr1: DWORD, + Dr2: DWORD, + Dr3: DWORD, + Dr6: DWORD, + Dr7: DWORD, + FloatSave: WOW64_FLOATING_SAVE_AREA, + SegGs: DWORD, + SegFs: DWORD, + SegEs: DWORD, + SegDs: DWORD, + Edi: DWORD, + Esi: DWORD, + Ebx: DWORD, + Edx: DWORD, + Ecx: DWORD, + Eax: DWORD, + Ebp: DWORD, + Eip: DWORD, + SegCs: DWORD, + EFlags: DWORD, + Esp: DWORD, + SegSs: DWORD, + ExtendedRegisters: [WOW64_MAXIMUM_SUPPORTED_EXTENSION]BYTE, +} + +PWOW64_CONTEXT :: ^WOW64_CONTEXT + +WOW64_FLOATING_SAVE_AREA :: struct { + ControlWord: DWORD, + StatusWord: DWORD, + TagWord: DWORD, + ErrorOffset: DWORD, + ErrorSelector: DWORD, + DataOffset: DWORD, + DataSelector: DWORD, + RegisterArea: [WOW64_SIZE_OF_80387_REGISTERS]BYTE, + Cr0NpxState: DWORD, +} + +PWOW64_FLOATING_SAVE_AREA :: ^WOW64_FLOATING_SAVE_AREA \ No newline at end of file diff --git a/core/testing/events.odin b/core/testing/events.odin new file mode 100644 index 000000000..bab35aaad --- /dev/null +++ b/core/testing/events.odin @@ -0,0 +1,48 @@ +//+private +package testing + +import "base:runtime" +import "core:sync/chan" +import "core:time" + +Test_State :: enum { + Ready, + Running, + Successful, + Failed, +} + +Update_Channel :: chan.Chan(Channel_Event) +Update_Channel_Sender :: chan.Chan(Channel_Event, .Send) + +Task_Channel :: struct { + channel: Update_Channel, + test_index: int, +} + +Event_New_Test :: struct { + test_index: int, +} + +Event_State_Change :: struct { + new_state: Test_State, +} + +Event_Set_Fail_Timeout :: struct { + at_time: time.Time, + location: runtime.Source_Code_Location, +} + +Event_Log_Message :: struct { + level: runtime.Logger_Level, + text: string, + time: time.Time, + formatted_text: string, +} + +Channel_Event :: union { + Event_New_Test, + Event_State_Change, + Event_Set_Fail_Timeout, + Event_Log_Message, +} diff --git a/core/testing/logging.odin b/core/testing/logging.odin new file mode 100644 index 000000000..f1e75d33c --- /dev/null +++ b/core/testing/logging.odin @@ -0,0 +1,80 @@ +//+private +package testing + +import "base:runtime" +import "core:fmt" +import pkg_log "core:log" +import "core:strings" +import "core:sync/chan" +import "core:time" + +when USING_SHORT_LOGS { + Default_Test_Logger_Opts :: runtime.Logger_Options { + .Level, + .Terminal_Color, + .Short_File_Path, + .Line, + } +} else { + Default_Test_Logger_Opts :: runtime.Logger_Options { + .Level, + .Terminal_Color, + .Short_File_Path, + .Line, + .Procedure, + .Date, .Time, + } +} + +Log_Message :: struct { + level: runtime.Logger_Level, + text: string, + time: time.Time, + // `text` may be allocated differently, depending on where a log message + // originates from. + allocator: runtime.Allocator, +} + +test_logger_proc :: proc(logger_data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location := #caller_location) { + t := cast(^T)logger_data + + if level >= .Error { + t.error_count += 1 + } + + cloned_text, clone_error := strings.clone(text, t._log_allocator) + assert(clone_error == nil, "Error while cloning string in test thread logger proc.") + + now := time.now() + + chan.send(t.channel, Event_Log_Message { + level = level, + text = cloned_text, + time = now, + formatted_text = format_log_text(level, text, options, location, now, t._log_allocator), + }) +} + +runner_logger_proc :: proc(logger_data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location := #caller_location) { + log_messages := cast(^[dynamic]Log_Message)logger_data + + now := time.now() + + append(log_messages, Log_Message { + level = level, + text = format_log_text(level, text, options, location, now), + time = now, + allocator = context.allocator, + }) +} + +format_log_text :: proc(level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location: runtime.Source_Code_Location, at_time: time.Time, allocator := context.allocator) -> string{ + 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) + + return fmt.aprintf("%s%s", strings.to_string(buf), text, allocator = allocator) +} diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin new file mode 100644 index 000000000..92e144ccc --- /dev/null +++ b/core/testing/reporting.odin @@ -0,0 +1,329 @@ +//+private +package testing + +import "base:runtime" +import "core:encoding/ansi" +import "core:fmt" +import "core:io" +import "core:mem" +import "core:path/filepath" +import "core:strings" + +// Definitions of colors for use in the test runner. +SGR_RESET :: ansi.CSI + ansi.RESET + ansi.SGR +SGR_READY :: ansi.CSI + ansi.FG_BRIGHT_BLACK + ansi.SGR +SGR_RUNNING :: ansi.CSI + ansi.FG_YELLOW + ansi.SGR +SGR_SUCCESS :: ansi.CSI + ansi.FG_GREEN + ansi.SGR +SGR_FAILED :: ansi.CSI + ansi.FG_RED + ansi.SGR + +MAX_PROGRESS_WIDTH :: 100 + +// More than enough bytes to cover long package names, long test names, dozens +// of ANSI codes, et cetera. +LINE_BUFFER_SIZE :: (MAX_PROGRESS_WIDTH * 8 + 224) * runtime.Byte + +PROGRESS_COLUMN_SPACING :: 2 + +Package_Run :: struct { + name: string, + header: string, + + frame_ready: bool, + + redraw_buffer: [LINE_BUFFER_SIZE]byte, + redraw_string: string, + + last_change_state: Test_State, + last_change_name: string, + + tests: []Internal_Test, + test_states: []Test_State, +} + +Report :: struct { + packages: []Package_Run, + packages_by_name: map[string]^Package_Run, + + pkg_column_len: int, + test_column_len: int, + progress_width: int, + + all_tests: []Internal_Test, + all_test_states: []Test_State, +} + +// Organize all tests by package and sort out test state data. +make_report :: proc(internal_tests: []Internal_Test) -> (report: Report, error: runtime.Allocator_Error) { + assert(len(internal_tests) > 0, "make_report called with no tests") + + packages: [dynamic]Package_Run + + report.all_tests = internal_tests + report.all_test_states = make([]Test_State, len(internal_tests)) or_return + + // First, figure out what belongs where. + #no_bounds_check cur_pkg := internal_tests[0].pkg + pkg_start: int + + // This loop assumes the tests are sorted by package already. + for it, index in internal_tests { + if cur_pkg != it.pkg { + #no_bounds_check { + append(&packages, Package_Run { + name = cur_pkg, + tests = report.all_tests[pkg_start:index], + test_states = report.all_test_states[pkg_start:index], + }) or_return + } + + when PROGRESS_WIDTH == 0 { + report.progress_width = max(report.progress_width, index - pkg_start) + } + + pkg_start = index + report.pkg_column_len = max(report.pkg_column_len, len(cur_pkg)) + cur_pkg = it.pkg + } + report.test_column_len = max(report.test_column_len, len(it.name)) + } + + // Handle the last (or only) package. + #no_bounds_check { + append(&packages, Package_Run { + name = cur_pkg, + header = cur_pkg, + tests = report.all_tests[pkg_start:], + test_states = report.all_test_states[pkg_start:], + }) or_return + } + when PROGRESS_WIDTH == 0 { + report.progress_width = max(report.progress_width, len(internal_tests) - pkg_start) + } else { + report.progress_width = PROGRESS_WIDTH + } + report.progress_width = min(report.progress_width, MAX_PROGRESS_WIDTH) + + report.pkg_column_len = PROGRESS_COLUMN_SPACING + max(report.pkg_column_len, len(cur_pkg)) + + shrink(&packages) or_return + + for &pkg in packages { + pkg.header = fmt.aprintf("%- *[1]s[", pkg.name, report.pkg_column_len) + assert(len(pkg.header) > 0, "Error allocating package header string.") + + // This is safe because the array is done resizing, and it has the same + // lifetime as the map. + report.packages_by_name[pkg.name] = &pkg + } + + // It's okay to discard the dynamic array's allocator information here, + // because its capacity has been shrunk to its length, it was allocated by + // the caller's context allocator, and it will be deallocated by the same. + // + // `delete_slice` is equivalent to `delete_dynamic_array` in this case. + report.packages = packages[:] + + return +} + +destroy_report :: proc(report: ^Report) { + for pkg in report.packages { + delete(pkg.header) + } + + delete(report.packages) + delete(report.packages_by_name) + delete(report.all_test_states) +} + +redraw_package :: proc(w: io.Writer, report: Report, pkg: ^Package_Run) { + if pkg.frame_ready { + io.write_string(w, pkg.redraw_string) + return + } + + // Write the output line here so we can cache it. + line_builder := strings.builder_from_bytes(pkg.redraw_buffer[:]) + line_writer := strings.to_writer(&line_builder) + + highest_run_index: int + failed_count: int + done_count: int + #no_bounds_check for i := 0; i < len(pkg.test_states); i += 1 { + switch pkg.test_states[i] { + case .Ready: + continue + case .Running: + highest_run_index = max(highest_run_index, i) + case .Successful: + done_count += 1 + case .Failed: + failed_count += 1 + done_count += 1 + } + } + + start := max(0, highest_run_index - (report.progress_width - 1)) + end := min(start + report.progress_width, len(pkg.test_states)) + + // This variable is to keep track of the last ANSI code emitted, in + // order to avoid repeating the same code over in a sequence. + // + // This should help reduce screen flicker. + last_state := Test_State(-1) + + io.write_string(line_writer, pkg.header) + + #no_bounds_check for state in pkg.test_states[start:end] { + switch state { + case .Ready: + if last_state != state { + io.write_string(line_writer, SGR_READY) + last_state = state + } + case .Running: + if last_state != state { + io.write_string(line_writer, SGR_RUNNING) + last_state = state + } + case .Successful: + if last_state != state { + io.write_string(line_writer, SGR_SUCCESS) + last_state = state + } + case .Failed: + if last_state != state { + io.write_string(line_writer, SGR_FAILED) + last_state = state + } + } + io.write_byte(line_writer, '|') + } + + for _ in 0 ..< report.progress_width - (end - start) { + io.write_byte(line_writer, ' ') + } + + io.write_string(line_writer, SGR_RESET + "] ") + + ticker: string + if done_count == len(pkg.test_states) { + ticker = "[package done]" + if failed_count > 0 { + ticker = fmt.tprintf("%s (" + SGR_FAILED + "%i" + SGR_RESET + " failed)", ticker, failed_count) + } + } else { + if len(pkg.last_change_name) == 0 { + #no_bounds_check pkg.last_change_name = pkg.tests[0].name + } + + switch pkg.last_change_state { + case .Ready: + ticker = fmt.tprintf(SGR_READY + "%s" + SGR_RESET, pkg.last_change_name) + case .Running: + ticker = fmt.tprintf(SGR_RUNNING + "%s" + SGR_RESET, pkg.last_change_name) + case .Failed: + ticker = fmt.tprintf(SGR_FAILED + "%s" + SGR_RESET, pkg.last_change_name) + case .Successful: + ticker = fmt.tprintf(SGR_SUCCESS + "%s" + SGR_RESET, pkg.last_change_name) + } + } + + if done_count == len(pkg.test_states) { + fmt.wprintfln(line_writer, " % 4i :: %s", + len(pkg.test_states), + ticker, + ) + } else { + fmt.wprintfln(line_writer, "% 4i/% 4i :: %s", + done_count, + len(pkg.test_states), + ticker, + ) + } + + pkg.redraw_string = strings.to_string(line_builder) + pkg.frame_ready = true + io.write_string(w, pkg.redraw_string) +} + +redraw_report :: proc(w: io.Writer, report: Report) { + // If we print a line longer than the user's terminal can handle, it may + // wrap around, shifting the progress report out of alignment. + // + // There are ways to get the current terminal width, and that would be the + // ideal way to handle this, but it would require system-specific code such + // as setting STDIN to be non-blocking in order to read the response from + // the ANSI DSR escape code, or reading environment variables. + // + // The DECAWM escape codes control whether or not the terminal will wrap + // long lines or overwrite the last visible character. + // This should be fine for now. + // + // Note that we only do this for the animated summary; log messages are + // still perfectly fine to wrap, as they're printed in their own batch, + // whereas the animation depends on each package being only on one line. + // + // Of course, if you resize your terminal while it's printing, things can + // still break... + fmt.wprint(w, ansi.CSI + ansi.DECAWM_OFF) + for &pkg in report.packages { + redraw_package(w, report, &pkg) + } + fmt.wprint(w, ansi.CSI + ansi.DECAWM_ON) +} + +needs_to_redraw :: proc(report: Report) -> bool { + for pkg in report.packages { + if !pkg.frame_ready { + return true + } + } + + return false +} + +draw_status_bar :: proc(w: io.Writer, threads_string: string, total_done_count, total_test_count: int) { + if total_done_count == total_test_count { + // All tests are done; print a blank line to maintain the same height + // of the progress report. + fmt.wprintln(w) + } else { + fmt.wprintfln(w, + "%s % 4i/% 4i :: total", + threads_string, + total_done_count, + total_test_count) + } +} + +write_memory_report :: proc(w: io.Writer, tracker: ^mem.Tracking_Allocator, pkg, name: string) { + fmt.wprintf(w, + "<% 10M/% 10M> <% 10M> (% 5i/% 5i) :: %s.%s", + tracker.current_memory_allocated, + tracker.total_memory_allocated, + tracker.peak_memory_allocated, + tracker.total_free_count, + tracker.total_allocation_count, + pkg, + name) + + for ptr, entry in tracker.allocation_map { + fmt.wprintf(w, + "\n +++ leak % 10M @ %p [%s:%i:%s()]", + entry.size, + ptr, + filepath.base(entry.location.file_path), + entry.location.line, + entry.location.procedure) + } + + for entry in tracker.bad_free_array { + fmt.wprintf(w, + "\n +++ bad free @ %p [%s:%i:%s()]", + entry.memory, + filepath.base(entry.location.file_path), + entry.location.line, + entry.location.procedure) + } +} diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 0039f1939..c27c2124a 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -1,73 +1,851 @@ //+private package testing +import "base:intrinsics" +import "base:runtime" +import "core:bytes" +import "core:encoding/ansi" +@require import "core:encoding/base64" +import "core:fmt" import "core:io" +@require import pkg_log "core:log" +import "core:math/rand" +import "core:mem" import "core:os" import "core:slice" +@require import "core:strings" +import "core:sync/chan" +import "core:thread" +import "core:time" -reset_t :: proc(t: ^T) { - clear(&t.cleanups) - t.error_count = 0 +// Specify how many threads to use when running tests. +TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0) +// Track the memory used by each test. +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) +// 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. +// Each test is separated by a comma and may optionally include the package name. +// This may be useful when running tests on multiple packages with `-all-packages`. +// The format is: `package.test_name,test_name_only,...` +TEST_NAMES : string : #config(ODIN_TEST_NAMES, "") +// Show the fancy animated progress report. +FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) +// Copy failed tests to the clipboard when done. +USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false) +// How many test results to show at a time per package. +PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24) +// This is the random seed that will be sent to each test. +// 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") +// Show only the most necessary logging information. +USING_SHORT_LOGS : bool : #config(ODIN_TEST_SHORT_LOGS, false) + + +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\".") + } + } } + end_t :: proc(t: ^T) { for i := len(t.cleanups)-1; i >= 0; i -= 1 { - c := t.cleanups[i] + #no_bounds_check c := t.cleanups[i] + context = c.ctx c.procedure(c.user_data) } + + delete(t.cleanups) + t.cleanups = {} +} + +Task_Data :: struct { + it: Internal_Test, + t: T, + allocator_index: int, +} + +Task_Timeout :: struct { + test_index: int, + at_time: time.Time, + location: runtime.Source_Code_Location, +} + +run_test_task :: proc(task: thread.Task) { + data := cast(^Task_Data)(task.data) + + setup_task_signal_handler(task.user_index) + + chan.send(data.t.channel, Event_New_Test { + test_index = task.user_index, + }) + + chan.send(data.t.channel, Event_State_Change { + new_state = .Running, + }) + + context.assertion_failure_proc = test_assertion_failure_proc + + context.logger = { + procedure = test_logger_proc, + data = &data.t, + lowest_level = get_log_level(), + options = Default_Test_Logger_Opts, + } + + random_generator_state: runtime.Default_Random_State + context.random_generator = { + procedure = runtime.default_random_generator_proc, + data = &random_generator_state, + } + rand.reset(data.t.seed) + + free_all(context.temp_allocator) + + data.it.p(&data.t) + + end_t(&data.t) + + new_state : Test_State = .Failed if failed(&data.t) else .Successful + + chan.send(data.t.channel, Event_State_Change { + new_state = new_state, + }) } runner :: proc(internal_tests: []Internal_Test) -> bool { - stream := os.stream_from_handle(os.stdout) - w := io.to_writer(stream) + BATCH_BUFFER_SIZE :: 32 * mem.Kilobyte + POOL_BLOCK_SIZE :: 16 * mem.Kilobyte + CLIPBOARD_BUFFER_SIZE :: 16 * mem.Kilobyte - t := &T{} - t.w = w - reserve(&t.cleanups, 1024) - defer delete(t.cleanups) + BUFFERED_EVENTS_PER_CHANNEL :: 16 + RESERVED_LOG_MESSAGES :: 64 + RESERVED_TEST_FAILURES :: 64 - total_success_count := 0 - total_test_count := len(internal_tests) + ERROR_STRING_TIMEOUT : string : "Test timed out." + ERROR_STRING_UNKNOWN : string : "Test failed for unknown reasons." + OSC_WINDOW_TITLE : string : ansi.OSC + ansi.WINDOW_TITLE + ";Odin test runner (%i/%i)" + ansi.ST - slice.sort_by(internal_tests, proc(a, b: Internal_Test) -> bool { - if a.pkg < b.pkg { - return true + safe_delete_string :: proc(s: string, allocator := context.allocator) { + // Guard against bad frees on static strings. + switch raw_data(s) { + case raw_data(ERROR_STRING_TIMEOUT), raw_data(ERROR_STRING_UNKNOWN): + return + case: + delete(s, allocator) } - return a.name < b.name - }) + } - prev_pkg := "" + stdout := io.to_writer(os.stream_from_handle(os.stdout)) + stderr := io.to_writer(os.stream_from_handle(os.stderr)) + + // -- Prepare test data. + + alloc_error: mem.Allocator_Error + + when TEST_NAMES != "" { + select_internal_tests: [dynamic]Internal_Test + defer delete(select_internal_tests) + + { + index_list := TEST_NAMES + for selector in strings.split_iterator(&index_list, ",") { + // Temp allocator is fine since we just need to identify which test it's referring to. + split_selector := strings.split(selector, ".", context.temp_allocator) + + found := false + switch len(split_selector) { + case 1: + // Only the test name? + #no_bounds_check name := split_selector[0] + find_test_by_name: for it in internal_tests { + if it.name == name { + found = true + _, alloc_error = append(&select_internal_tests, it) + fmt.assertf(alloc_error == nil, "Error appending to select internal tests: %v", alloc_error) + break find_test_by_name + } + } + case 2: + #no_bounds_check pkg := split_selector[0] + #no_bounds_check name := split_selector[1] + find_test_by_pkg_and_name: for it in internal_tests { + if it.pkg == pkg && it.name == name { + found = true + _, alloc_error = append(&select_internal_tests, it) + fmt.assertf(alloc_error == nil, "Error appending to select internal tests: %v", alloc_error) + break find_test_by_pkg_and_name + } + } + } + if !found { + fmt.wprintfln(stderr, "No test found for the name: %q", selector) + } + } + } + + // `-vet` needs parameters to be shadowed by themselves first as an + // explicit declaration, to allow the next line to work. + internal_tests := internal_tests + // Intentional shadow with user-specified tests. + internal_tests = select_internal_tests[:] + } + + total_failure_count := 0 + total_success_count := 0 + total_done_count := 0 + total_test_count := len(internal_tests) + + when !FANCY_OUTPUT { + // This is strictly for updating the window title when the progress + // report is disabled. We're otherwise able to depend on the call to + // `needs_to_redraw`. + last_done_count := -1 + } + + if total_test_count == 0 { + // Exit early. + fmt.wprintln(stdout, "No tests to run.") + return true + } for it in internal_tests { - if it.p == nil { - total_test_count -= 1 - continue - } + // NOTE(Feoramund): The old test runner skipped over tests with nil + // procedures, but I couldn't find any case where they occurred. + // This assert stands to prevent any oversight on my part. + fmt.assertf(it.p != nil, "Test %s.%s has procedure.", it.pkg, it.name) + } - free_all(context.temp_allocator) - reset_t(t) - defer end_t(t) - - if prev_pkg != it.pkg { - prev_pkg = it.pkg - logf(t, "[Package: %s]", it.pkg) - } - - logf(t, "[Test: %s]", it.name) - - run_internal_test(t, it) - - if failed(t) { - logf(t, "[%s : FAILURE]", it.name) + slice.sort_by(internal_tests, proc(a, b: Internal_Test) -> bool { + if a.pkg == b.pkg { + return a.name < b.name } else { - logf(t, "[%s : SUCCESS]", it.name) - total_success_count += 1 + return a.pkg < b.pkg + } + }) + + // -- Set thread count. + + when TEST_THREADS == 0 { + thread_count := os.processor_core_count() + } else { + thread_count := max(1, TEST_THREADS) + } + + thread_count = min(thread_count, total_test_count) + + // -- Allocate. + + pool_stack: mem.Rollback_Stack + alloc_error = mem.rollback_stack_init(&pool_stack, POOL_BLOCK_SIZE) + fmt.assertf(alloc_error == nil, "Error allocating memory for thread pool: %v", alloc_error) + defer mem.rollback_stack_destroy(&pool_stack) + + pool: thread.Pool + thread.pool_init(&pool, mem.rollback_stack_allocator(&pool_stack), thread_count) + defer thread.pool_destroy(&pool) + + task_channels: []Task_Channel = --- + task_channels, alloc_error = make([]Task_Channel, thread_count) + fmt.assertf(alloc_error == nil, "Error allocating memory for update channels: %v", alloc_error) + defer delete(task_channels) + + for &task_channel, index in task_channels { + task_channel.channel, alloc_error = chan.create_buffered(Update_Channel, BUFFERED_EVENTS_PER_CHANNEL, context.allocator) + fmt.assertf(alloc_error == nil, "Error allocating memory for update channel #%i: %v", index, alloc_error) + } + defer for &task_channel in task_channels { + chan.destroy(&task_channel.channel) + } + + // This buffer is used to batch writes to STDOUT or STDERR, to help reduce + // screen flickering. + batch_buffer: bytes.Buffer + bytes.buffer_init_allocator(&batch_buffer, 0, BATCH_BUFFER_SIZE) + batch_writer := io.to_writer(bytes.buffer_to_stream(&batch_buffer)) + defer bytes.buffer_destroy(&batch_buffer) + + report: Report = --- + report, alloc_error = make_report(internal_tests) + fmt.assertf(alloc_error == nil, "Error allocating memory for test report: %v", alloc_error) + defer destroy_report(&report) + + when FANCY_OUTPUT { + // We cannot make use of the ANSI save/restore cursor codes, because they + // work by absolute screen coordinates. This will cause unnecessary + // scrollback if we print at the bottom of someone's terminal. + ansi_redraw_string := fmt.aprintf( + // ANSI for "go up N lines then erase the screen from the cursor forward." + ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED + + // We'll combine this with the window title format string, since it + // can be printed at the same time. + "%s", + // 1 extra line for the status bar. + 1 + len(report.packages), OSC_WINDOW_TITLE) + assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.") + defer delete(ansi_redraw_string) + + thread_count_status_string: string = --- + { + PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH + + unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s") + thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING) + assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.") + } + defer delete(thread_count_status_string) + } + + task_data_slots: []Task_Data = --- + task_data_slots, alloc_error = make([]Task_Data, thread_count) + fmt.assertf(alloc_error == nil, "Error allocating memory for task data slots: %v", alloc_error) + defer delete(task_data_slots) + + // Tests rotate through these allocators as they finish. + task_allocators: []mem.Rollback_Stack = --- + task_allocators, alloc_error = make([]mem.Rollback_Stack, thread_count) + fmt.assertf(alloc_error == nil, "Error allocating memory for task allocators: %v", alloc_error) + defer delete(task_allocators) + + when TRACKING_MEMORY { + task_memory_trackers: []mem.Tracking_Allocator = --- + task_memory_trackers, alloc_error = make([]mem.Tracking_Allocator, thread_count) + fmt.assertf(alloc_error == nil, "Error allocating memory for memory trackers: %v", alloc_error) + defer delete(task_memory_trackers) + } + + #no_bounds_check for i in 0 ..< thread_count { + alloc_error = mem.rollback_stack_init(&task_allocators[i], PER_THREAD_MEMORY) + fmt.assertf(alloc_error == nil, "Error allocating memory for task allocator #%i: %v", i, alloc_error) + when TRACKING_MEMORY { + mem.tracking_allocator_init(&task_memory_trackers[i], mem.rollback_stack_allocator(&task_allocators[i])) } } - logf(t, "----------------------------------------") - if total_test_count == 0 { - log(t, "NO TESTS RAN") - } else { - logf(t, "%d/%d SUCCESSFUL", total_success_count, total_test_count) + + defer #no_bounds_check for i in 0 ..< thread_count { + when TRACKING_MEMORY { + mem.tracking_allocator_destroy(&task_memory_trackers[i]) + } + mem.rollback_stack_destroy(&task_allocators[i]) } + + task_timeouts: [dynamic]Task_Timeout = --- + task_timeouts, alloc_error = make([dynamic]Task_Timeout, 0, thread_count) + fmt.assertf(alloc_error == nil, "Error allocating memory for task timeouts: %v", alloc_error) + defer delete(task_timeouts) + + failed_test_reason_map: map[int]string = --- + failed_test_reason_map, alloc_error = make(map[int]string, RESERVED_TEST_FAILURES) + fmt.assertf(alloc_error == nil, "Error allocating memory for failed test reasons: %v", alloc_error) + defer delete(failed_test_reason_map) + + log_messages: [dynamic]Log_Message = --- + log_messages, alloc_error = make([dynamic]Log_Message, 0, RESERVED_LOG_MESSAGES) + fmt.assertf(alloc_error == nil, "Error allocating memory for log message queue: %v", alloc_error) + defer delete(log_messages) + + sorted_failed_test_reasons: [dynamic]int = --- + sorted_failed_test_reasons, alloc_error = make([dynamic]int, 0, RESERVED_TEST_FAILURES) + fmt.assertf(alloc_error == nil, "Error allocating memory for sorted failed test reasons: %v", alloc_error) + defer delete(sorted_failed_test_reasons) + + when USE_CLIPBOARD { + clipboard_buffer: bytes.Buffer + bytes.buffer_init_allocator(&clipboard_buffer, 0, CLIPBOARD_BUFFER_SIZE) + defer bytes.buffer_destroy(&clipboard_buffer) + } + + when SHARED_RANDOM_SEED == 0 { + shared_random_seed := cast(u64)intrinsics.read_cycle_counter() + } else { + shared_random_seed := SHARED_RANDOM_SEED + } + + // -- Setup initial tasks. + + // NOTE(Feoramund): This is the allocator that will be used by threads to + // persist log messages past their lifetimes. It has its own variable name + // in the event it needs to be changed from `context.allocator` without + // digging through the source to divine everywhere it is used for that. + shared_log_allocator := context.allocator + + context.logger = { + procedure = runner_logger_proc, + data = &log_messages, + lowest_level = get_log_level(), + options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}, + } + + run_index: int + + setup_tasks: for &data, task_index in task_data_slots { + setup_next_test: for run_index < total_test_count { + #no_bounds_check it := internal_tests[run_index] + defer run_index += 1 + + data.it = it + data.t.seed = shared_random_seed + #no_bounds_check data.t.channel = chan.as_send(task_channels[task_index].channel) + data.t._log_allocator = shared_log_allocator + data.allocator_index = task_index + + #no_bounds_check when TRACKING_MEMORY { + task_allocator := mem.tracking_allocator(&task_memory_trackers[task_index]) + } else { + task_allocator := mem.rollback_stack_allocator(&task_allocators[task_index]) + } + + thread.pool_add_task(&pool, task_allocator, run_test_task, &data, run_index) + + continue setup_tasks + } + } + + // -- Run tests. + + setup_signal_handler() + + fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE) + + when FANCY_OUTPUT { + signals_were_raised := false + + redraw_report(stdout, report) + draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count) + } + + when TEST_THREADS == 0 { + pkg_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.", + 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) + } else { + pkg_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.") + } else { + pkg_log.info("Memory tracking is enabled. Tests will log their memory usage if there's an issue.") + } + 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() + + thread.pool_start(&pool) + main_loop: for !thread.pool_is_empty(&pool) { + { + events_pending := thread.pool_num_done(&pool) > 0 + + if !events_pending { + poll_tasks: for &task_channel in task_channels { + if chan.len(task_channel.channel) > 0 { + events_pending = true + break poll_tasks + } + } + } + + if !events_pending { + // Keep the main thread from pegging a core at 100% usage. + time.sleep(1 * time.Microsecond) + } + } + + cycle_pool: for task in thread.pool_pop_done(&pool) { + data := cast(^Task_Data)(task.data) + + when TRACKING_MEMORY { + #no_bounds_check tracker := &task_memory_trackers[data.allocator_index] + + memory_is_in_bad_state := len(tracker.allocation_map) + len(tracker.bad_free_array) > 0 + + when ALWAYS_REPORT_MEMORY { + should_report := true + } else { + should_report := memory_is_in_bad_state + } + + 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)) + bytes.buffer_reset(&batch_buffer) + } + + mem.tracking_allocator_reset(tracker) + } + + free_all(task.allocator) + + if run_index < total_test_count { + #no_bounds_check it := internal_tests[run_index] + defer run_index += 1 + + data.it = it + data.t.seed = shared_random_seed + data.t.error_count = 0 + data.t._fail_now_called = false + + thread.pool_add_task(&pool, task.allocator, run_test_task, data, run_index) + } + } + + handle_events: for &task_channel in task_channels { + for ev in chan.try_recv(task_channel.channel) { + switch event in ev { + case Event_New_Test: + task_channel.test_index = event.test_index + + case Event_State_Change: + #no_bounds_check report.all_test_states[task_channel.test_index] = event.new_state + + #no_bounds_check it := internal_tests[task_channel.test_index] + #no_bounds_check pkg := report.packages_by_name[it.pkg] + + #partial switch event.new_state { + case .Failed: + if task_channel.test_index not_in failed_test_reason_map { + failed_test_reason_map[task_channel.test_index] = ERROR_STRING_UNKNOWN + } + total_failure_count += 1 + total_done_count += 1 + case .Successful: + total_success_count += 1 + total_done_count += 1 + } + + 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) + } + + pkg.last_change_state = event.new_state + pkg.last_change_name = it.name + pkg.frame_ready = false + + case Event_Set_Fail_Timeout: + _, alloc_error = append(&task_timeouts, Task_Timeout { + test_index = task_channel.test_index, + at_time = event.at_time, + location = event.location, + }) + fmt.assertf(alloc_error == nil, "Error appending to task timeouts: %v", alloc_error) + + case Event_Log_Message: + _, alloc_error = append(&log_messages, Log_Message { + level = event.level, + text = event.formatted_text, + time = event.time, + allocator = shared_log_allocator, + }) + fmt.assertf(alloc_error == nil, "Error appending to log messages: %v", alloc_error) + + if event.level >= .Error { + // Save the message for the final summary. + if old_error, ok := failed_test_reason_map[task_channel.test_index]; ok { + safe_delete_string(old_error, shared_log_allocator) + } + failed_test_reason_map[task_channel.test_index] = event.text + } else { + delete(event.text, shared_log_allocator) + } + } + } + } + + check_timeouts: for i := len(task_timeouts) - 1; i >= 0; i -= 1 { + #no_bounds_check timeout := &task_timeouts[i] + + if time.since(timeout.at_time) < 0 { + continue check_timeouts + } + + defer unordered_remove(&task_timeouts, i) + + #no_bounds_check if report.all_test_states[timeout.test_index] > .Running { + continue check_timeouts + } + + if !thread.pool_stop_task(&pool, timeout.test_index) { + // The task may have stopped a split second after we started + // checking, but we haven't handled the new state yet. + continue check_timeouts + } + + #no_bounds_check report.all_test_states[timeout.test_index] = .Failed + #no_bounds_check it := internal_tests[timeout.test_index] + #no_bounds_check pkg := report.packages_by_name[it.pkg] + pkg.frame_ready = false + + if old_error, ok := failed_test_reason_map[timeout.test_index]; ok { + safe_delete_string(old_error, shared_log_allocator) + } + failed_test_reason_map[timeout.test_index] = ERROR_STRING_TIMEOUT + total_failure_count += 1 + total_done_count += 1 + + now := time.now() + _, alloc_error = append(&log_messages, Log_Message { + level = .Error, + text = format_log_text(.Error, ERROR_STRING_TIMEOUT, Default_Test_Logger_Opts, timeout.location, now), + time = now, + allocator = context.allocator, + }) + fmt.assertf(alloc_error == nil, "Error appending to log messages: %v", alloc_error) + + find_task_data_for_timeout: for &data in task_data_slots { + if data.it.pkg == it.pkg && data.it.name == it.name { + end_t(&data.t) + break find_task_data_for_timeout + } + } + } + + if should_stop_runner() { + fmt.wprintln(stderr, "\nCaught interrupt signal. Stopping all tests.") + thread.pool_shutdown(&pool) + break main_loop + } + + when FANCY_OUTPUT { + // Because the bounds checking procs send directly to STDERR with + // no way to redirect or handle them, we need to at least try to + // let the user see those messages when using the animated progress + // report. This flag may be set by the block of code below if a + // signal is raised. + // + // It'll be purely by luck if the output is interleaved properly, + // given the nature of non-thread-safe printing. + // + // At worst, if Odin did not print any error for this signal, we'll + // just re-display the progress report. The fatal log error message + // should be enough to clue the user in that something dire has + // occurred. + bypass_progress_overwrite := false + } + + if test_index, reason, ok := should_stop_test(); ok { + #no_bounds_check report.all_test_states[test_index] = .Failed + #no_bounds_check it := internal_tests[test_index] + #no_bounds_check pkg := report.packages_by_name[it.pkg] + pkg.frame_ready = false + + found := thread.pool_stop_task(&pool, test_index) + fmt.assertf(found, "A signal (%v) was raised to stop test #%i %s.%s, but it was unable to be found.", + reason, test_index, it.pkg, it.name) + + // The order this is handled in is a little particular. + task_data: ^Task_Data + find_task_data_for_stop_signal: for &data in task_data_slots { + if data.it.pkg == it.pkg && data.it.name == it.name { + task_data = &data + break find_task_data_for_stop_signal + } + } + + fmt.assertf(task_data != nil, "A signal (%v) was raised to stop test #%i %s.%s, but its task data is missing.", + reason, test_index, it.pkg, it.name) + + if !task_data.t._fail_now_called { + if test_index not_in failed_test_reason_map { + // We only write a new error message here if there wasn't one + // already, because the message we can provide based only on + // 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) + } + + when FANCY_OUTPUT { + bypass_progress_overwrite = true + signals_were_raised = true + } + } + + end_t(&task_data.t) + + total_failure_count += 1 + total_done_count += 1 + } + + // -- Redraw. + + when FANCY_OUTPUT { + if len(log_messages) == 0 && !needs_to_redraw(report) { + continue main_loop + } + + if !bypass_progress_overwrite { + fmt.wprintf(stdout, ansi_redraw_string, total_done_count, total_test_count) + } + } else { + if total_done_count != last_done_count { + fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count) + last_done_count = total_done_count + } + + if len(log_messages) == 0 { + continue main_loop + } + } + + // Because each thread has its own messenger channel, log messages + // arrive in chunks that are in-order, but when they're merged with the + // logs from other threads, they become out-of-order. + slice.stable_sort_by(log_messages[:], proc(a, b: Log_Message) -> bool { + return time.diff(a.time, b.time) > 0 + }) + + for message in log_messages { + fmt.wprintln(batch_writer, message.text) + delete(message.text, message.allocator) + } + + fmt.wprint(stderr, bytes.buffer_to_string(&batch_buffer)) + clear(&log_messages) + bytes.buffer_reset(&batch_buffer) + + when FANCY_OUTPUT { + redraw_report(batch_writer, report) + draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count) + fmt.wprint(stdout, bytes.buffer_to_string(&batch_buffer)) + bytes.buffer_reset(&batch_buffer) + } + } + + // -- All tests are complete, or the runner has been interrupted. + + // NOTE(Feoramund): If you've arrived here after receiving signal 11 or + // SIGSEGV on the main runner thread, while using a UNIX-like platform, + // there is the possibility that you may have encountered a rare edge case + // involving the joining of threads. + // + // At the time of writing, the thread library is undergoing a rewrite that + // should solve this problem; it is not an issue with the test runner itself. + thread.pool_join(&pool) + + finished_in := time.since(start_time) + + when !FANCY_OUTPUT { + // One line to space out the results, since we don't have the status + // bar in plain mode. + fmt.wprintln(batch_writer) + } + + fmt.wprintf(batch_writer, + "Finished %i test%s in %v.", + total_done_count, + "" if total_done_count == 1 else "s", + finished_in) + + if total_done_count != total_test_count { + not_run_count := total_test_count - total_done_count + fmt.wprintf(batch_writer, + " " + SGR_READY + "%i" + SGR_RESET + " %s left undone.", + not_run_count, + "test was" if not_run_count == 1 else "tests were") + } + + if total_success_count == total_test_count { + fmt.wprintfln(batch_writer, + " %s " + SGR_SUCCESS + "successful." + SGR_RESET, + "The test was" if total_test_count == 1 else "All tests were") + } else if total_failure_count > 0 { + if total_failure_count == total_test_count { + fmt.wprintfln(batch_writer, + " %s " + SGR_FAILED + "failed." + SGR_RESET, + "The test" if total_test_count == 1 else "All tests") + } else { + fmt.wprintfln(batch_writer, + " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed.", + total_failure_count, + "" if total_failure_count == 1 else "s") + } + + for test_index in failed_test_reason_map { + _, alloc_error = append(&sorted_failed_test_reasons, test_index) + fmt.assertf(alloc_error == nil, "Error appending to sorted failed test reasons: %v", alloc_error) + } + + slice.sort(sorted_failed_test_reasons[:]) + + for test_index in sorted_failed_test_reasons { + #no_bounds_check last_error := failed_test_reason_map[test_index] + #no_bounds_check it := internal_tests[test_index] + pkg_and_name := fmt.tprintf("%s.%s", it.pkg, it.name) + fmt.wprintfln(batch_writer, " - %- *[1]s\t%s", + pkg_and_name, + report.pkg_column_len + report.test_column_len, + last_error) + safe_delete_string(last_error, shared_log_allocator) + } + + if total_success_count > 0 { + when USE_CLIPBOARD { + clipboard_writer := io.to_writer(bytes.buffer_to_stream(&clipboard_buffer)) + fmt.wprint(clipboard_writer, "-define:ODIN_TEST_NAMES=") + for test_index in sorted_failed_test_reasons { + #no_bounds_check it := internal_tests[test_index] + fmt.wprintf(clipboard_writer, "%s.%s,", it.pkg, it.name) + } + + encoded_names := base64.encode(bytes.buffer_to_bytes(&clipboard_buffer), allocator = context.temp_allocator) + + fmt.wprintf(batch_writer, + ansi.OSC + ansi.CLIPBOARD + ";c;%s" + ansi.ST + + "\nThe name%s of the failed test%s been copied to your clipboard.", + encoded_names, + "" if total_failure_count == 1 else "s", + " has" if total_failure_count == 1 else "s have") + } else { + fmt.wprintf(batch_writer, "\nTo run only the failed test%s, use:\n\t-define:ODIN_TEST_NAMES=", + "" if total_failure_count == 1 else "s") + for test_index in sorted_failed_test_reasons { + #no_bounds_check it := internal_tests[test_index] + fmt.wprintf(batch_writer, "%s.%s,", it.pkg, it.name) + } + fmt.wprint(batch_writer, "\n\nIf your terminal supports OSC 52, you may use -define:ODIN_TEST_CLIPBOARD to have this copied directly to your clipboard.") + } + + fmt.wprintln(batch_writer) + } + } + + fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW) + + when FANCY_OUTPUT { + if signals_were_raised { + fmt.wprintln(batch_writer, ` +Signals were raised during this test run. Log messages are likely to have collided with each other. +To partly mitigate this, redirect STDERR to a file or use the -define:ODIN_TEST_FANCY=false option.`) + } + } + + fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer)) + return total_success_count == total_test_count } diff --git a/core/testing/runner_other.odin b/core/testing/runner_other.odin deleted file mode 100644 index f3271d209..000000000 --- a/core/testing/runner_other.odin +++ /dev/null @@ -1,14 +0,0 @@ -//+private -//+build !windows -package testing - -import "core:time" - -run_internal_test :: proc(t: ^T, it: Internal_Test) { - // TODO(bill): Catch panics on other platforms - it.p(t) -} - -_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) { - -} \ No newline at end of file diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin deleted file mode 100644 index 15264355b..000000000 --- a/core/testing/runner_windows.odin +++ /dev/null @@ -1,235 +0,0 @@ -//+private -//+build windows -package testing - -import win32 "core:sys/windows" -import "base:runtime" -import "base:intrinsics" -import "core:time" - -Sema :: struct { - count: i32, -} - -sema_reset :: proc "contextless" (s: ^Sema) { - intrinsics.atomic_store(&s.count, 0) -} -sema_wait :: proc "contextless" (s: ^Sema) { - for { - original_count := s.count - for original_count == 0 { - win32.WaitOnAddress(&s.count, &original_count, size_of(original_count), win32.INFINITE) - original_count = s.count - } - if original_count == intrinsics.atomic_compare_exchange_strong(&s.count, original_count-1, original_count) { - return - } - } -} -sema_wait_with_timeout :: proc "contextless" (s: ^Sema, duration: time.Duration) -> bool { - if duration <= 0 { - return false - } - for { - - original_count := intrinsics.atomic_load(&s.count) - for start := time.tick_now(); original_count == 0; /**/ { - if intrinsics.atomic_load(&s.count) != original_count { - remaining := duration - time.tick_since(start) - if remaining < 0 { - return false - } - ms := u32(remaining/time.Millisecond) - if !win32.WaitOnAddress(&s.count, &original_count, size_of(original_count), ms) { - return false - } - } - original_count = s.count - } - if original_count == intrinsics.atomic_compare_exchange_strong(&s.count, original_count-1, original_count) { - return true - } - } -} - -sema_post :: proc "contextless" (s: ^Sema, count := 1) { - intrinsics.atomic_add(&s.count, i32(count)) - if count == 1 { - win32.WakeByAddressSingle(&s.count) - } else { - win32.WakeByAddressAll(&s.count) - } -} - - - -Thread_Proc :: #type proc(^Thread) - -MAX_USER_ARGUMENTS :: 8 - -Thread :: struct { - using specific: Thread_Os_Specific, - procedure: Thread_Proc, - - t: ^T, - it: Internal_Test, - success: bool, - - init_context: Maybe(runtime.Context), - - creation_allocator: runtime.Allocator, - - internal_fail_timeout: time.Duration, - internal_fail_timeout_loc: runtime.Source_Code_Location, -} - -Thread_Os_Specific :: struct { - win32_thread: win32.HANDLE, - win32_thread_id: win32.DWORD, - done: bool, // see note in `is_done` -} - -thread_create :: proc(procedure: Thread_Proc) -> ^Thread { - __windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD { - t := (^Thread)(t_) - context = t.init_context.? or_else runtime.default_context() - - t.procedure(t) - - if t.init_context == nil { - if context.temp_allocator.data == &runtime.global_default_temp_allocator_data { - runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data) - } - } - - intrinsics.atomic_store(&t.done, true) - return 0 - } - - - thread := new(Thread) - if thread == nil { - return nil - } - thread.creation_allocator = context.allocator - - win32_thread_id: win32.DWORD - win32_thread := win32.CreateThread(nil, 0, __windows_thread_entry_proc, thread, win32.CREATE_SUSPENDED, &win32_thread_id) - if win32_thread == nil { - free(thread, thread.creation_allocator) - return nil - } - thread.procedure = procedure - thread.win32_thread = win32_thread - thread.win32_thread_id = win32_thread_id - thread.init_context = context - - return thread -} - -thread_start :: proc "contextless" (thread: ^Thread) { - win32.ResumeThread(thread.win32_thread) -} - -thread_join_and_destroy :: proc(thread: ^Thread) { - if thread.win32_thread != win32.INVALID_HANDLE { - win32.WaitForSingleObject(thread.win32_thread, win32.INFINITE) - win32.CloseHandle(thread.win32_thread) - thread.win32_thread = win32.INVALID_HANDLE - } - free(thread, thread.creation_allocator) -} - -thread_terminate :: proc "contextless" (thread: ^Thread, exit_code: int) { - win32.TerminateThread(thread.win32_thread, u32(exit_code)) -} - - -_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) { - assert(global_fail_timeout_thread == nil, "set_fail_timeout previously called", loc) - - thread := thread_create(proc(thread: ^Thread) { - t := thread.t - timeout := thread.internal_fail_timeout - if !sema_wait_with_timeout(&global_fail_timeout_semaphore, timeout) { - fail_now(t, "TIMEOUT", thread.internal_fail_timeout_loc) - } - }) - thread.internal_fail_timeout = duration - thread.internal_fail_timeout_loc = loc - thread.t = t - global_fail_timeout_thread = thread - thread_start(thread) -} - -global_fail_timeout_thread: ^Thread -global_fail_timeout_semaphore: Sema - -global_threaded_runner_semaphore: Sema -global_exception_handler: rawptr -global_current_thread: ^Thread -global_current_t: ^T - -run_internal_test :: proc(t: ^T, it: Internal_Test) { - thread := thread_create(proc(thread: ^Thread) { - exception_handler_proc :: proc "system" (ExceptionInfo: ^win32.EXCEPTION_POINTERS) -> win32.LONG { - switch ExceptionInfo.ExceptionRecord.ExceptionCode { - case - win32.EXCEPTION_DATATYPE_MISALIGNMENT, - win32.EXCEPTION_BREAKPOINT, - win32.EXCEPTION_ACCESS_VIOLATION, - win32.EXCEPTION_ILLEGAL_INSTRUCTION, - win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED, - win32.EXCEPTION_STACK_OVERFLOW: - - sema_post(&global_threaded_runner_semaphore) - return win32.EXCEPTION_EXECUTE_HANDLER - } - - return win32.EXCEPTION_CONTINUE_SEARCH - } - global_exception_handler = win32.AddVectoredExceptionHandler(0, exception_handler_proc) - - context.assertion_failure_proc = proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! { - errorf(global_current_t, "%s %s", prefix, message, loc=loc) - intrinsics.trap() - } - - t := thread.t - - global_fail_timeout_thread = nil - sema_reset(&global_fail_timeout_semaphore) - - thread.it.p(t) - - sema_post(&global_fail_timeout_semaphore) - if global_fail_timeout_thread != nil do thread_join_and_destroy(global_fail_timeout_thread) - - thread.success = true - sema_post(&global_threaded_runner_semaphore) - }) - - sema_reset(&global_threaded_runner_semaphore) - global_current_t = t - - t._fail_now = proc() -> ! { - intrinsics.trap() - } - - thread.t = t - thread.it = it - thread.success = false - thread_start(thread) - - sema_wait(&global_threaded_runner_semaphore) - thread_terminate(thread, int(!thread.success)) - thread_join_and_destroy(thread) - - win32.RemoveVectoredExceptionHandler(global_exception_handler) - - if !thread.success && t.error_count == 0 { - t.error_count += 1 - } - - return -} diff --git a/core/testing/signal_handler.odin b/core/testing/signal_handler.odin new file mode 100644 index 000000000..0b06852ce --- /dev/null +++ b/core/testing/signal_handler.odin @@ -0,0 +1,34 @@ +//+private +package testing + +import "base:runtime" +import pkg_log "core:log" + +Stop_Reason :: enum { + Unknown, + Illegal_Instruction, + Arithmetic_Error, + Segmentation_Fault, + Unhandled_Trap, +} + +test_assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! { + pkg_log.fatalf("%s: %s", prefix, message, location = loc) + runtime.trap() +} + +setup_signal_handler :: proc() { + _setup_signal_handler() +} + +setup_task_signal_handler :: proc(test_index: int) { + _setup_task_signal_handler(test_index) +} + +should_stop_runner :: proc() -> bool { + return _should_stop_runner() +} + +should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) { + return _should_stop_test() +} diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin new file mode 100644 index 000000000..d89d84fae --- /dev/null +++ b/core/testing/signal_handler_libc.odin @@ -0,0 +1,159 @@ +//+private +//+build windows, linux, darwin, freebsd, openbsd, netbsd, haiku +package testing + +import "base:intrinsics" +import "core:c/libc" +import "core:encoding/ansi" +import "core:sync" +import "core:os" +@require import "core:sys/unix" + +@(private="file") stop_runner_flag: libc.sig_atomic_t + +@(private="file") stop_test_gate: sync.Mutex +@(private="file") stop_test_index: libc.sig_atomic_t +@(private="file") stop_test_reason: libc.sig_atomic_t +@(private="file") stop_test_alert: libc.sig_atomic_t + +@(private="file", thread_local) +local_test_index: libc.sig_atomic_t + +// Windows does not appear to have a SIGTRAP, so this is defined here, instead +// of in the libc package, just so there's no confusion about it being +// available there. +SIGTRAP :: 5 + +@(private="file") +stop_runner_callback :: proc "c" (sig: libc.int) { + prev := intrinsics.atomic_add(&stop_runner_flag, 1) + + // If the flag was already set (if this is the second signal sent for example), + // consider this a forced (not graceful) exit. + if prev > 0 { + os.exit(int(sig)) + } +} + +@(private="file") +stop_test_callback :: proc "c" (sig: libc.int) { + if local_test_index == -1 { + // We're the test runner, and we ourselves have caught a signal from + // which there is no recovery. + // + // The most we can do now is make sure the user's cursor is visible, + // nuke the entire processs, and hope a useful core dump survives. + + // NOTE(Feoramund): Using these write calls in a signal handler is + // undefined behavior in C99 but possibly tolerated in POSIX 2008. + // Either way, we may as well try to salvage what we can. + show_cursor := ansi.CSI + ansi.DECTCEM_SHOW + libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout) + libc.fflush(libc.stdout) + + // This is an attempt at being compliant by avoiding printf. + sigbuf: [8]byte + sigstr: string + { + signum := cast(int)sig + i := len(sigbuf) - 2 + for signum > 0 { + m := signum % 10 + signum /= 10 + sigbuf[i] = cast(u8)('0' + m) + i -= 1 + } + sigstr = cast(string)sigbuf[1 + i:len(sigbuf) - 1] + } + + advisory_a := ` +The test runner's main thread has caught an unrecoverable error (signal ` + advisory_b := `) and will now forcibly terminate. +This is a dire bug and should be reported to the Odin developers. +` + libc.fwrite(raw_data(advisory_a), size_of(byte), len(advisory_a), libc.stderr) + libc.fwrite(raw_data(sigstr), size_of(byte), len(sigstr), libc.stderr) + libc.fwrite(raw_data(advisory_b), size_of(byte), len(advisory_b), libc.stderr) + + // Try to get a core dump. + libc.abort() + } + + if sync.mutex_guard(&stop_test_gate) { + intrinsics.atomic_store(&stop_test_index, local_test_index) + intrinsics.atomic_store(&stop_test_reason, cast(libc.sig_atomic_t)sig) + intrinsics.atomic_store(&stop_test_alert, 1) + + for { + // Idle until this thread is terminated by the runner, + // otherwise we may continue to generate signals. + intrinsics.cpu_relax() + + when ODIN_OS != .Windows { + // NOTE(Feoramund): Some UNIX-like platforms may require this. + // + // During testing, I found that NetBSD 10.0 refused to + // terminate a task thread, even when its thread had been + // properly set to PTHREAD_CANCEL_ASYNCHRONOUS. + // + // The runner would stall after returning from `pthread_cancel`. + + unix.pthread_testcancel() + } + } + } +} + +_setup_signal_handler :: proc() { + local_test_index = -1 + + // Catch user interrupt / CTRL-C. + libc.signal(libc.SIGINT, stop_runner_callback) + // Catch polite termination request. + libc.signal(libc.SIGTERM, stop_runner_callback) + + // For tests: + // Catch asserts and panics. + libc.signal(libc.SIGILL, stop_test_callback) + when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Darwin { + // Catch panics on Darwin and unhandled calls to `debug_trap`. + libc.signal(SIGTRAP, stop_test_callback) + } + // Catch arithmetic errors. + libc.signal(libc.SIGFPE, stop_test_callback) + // Catch segmentation faults (illegal memory access). + libc.signal(libc.SIGSEGV, stop_test_callback) +} + +_setup_task_signal_handler :: proc(test_index: int) { + local_test_index = cast(libc.sig_atomic_t)test_index +} + +_should_stop_runner :: proc() -> bool { + return intrinsics.atomic_load(&stop_runner_flag) == 1 +} + +@(private="file") +unlock_stop_test_gate :: proc(_: int, _: Stop_Reason, ok: bool) { + if ok { + sync.mutex_unlock(&stop_test_gate) + } +} + +@(deferred_out=unlock_stop_test_gate) +_should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) { + if intrinsics.atomic_load(&stop_test_alert) == 1 { + intrinsics.atomic_store(&stop_test_alert, 0) + + test_index = cast(int)intrinsics.atomic_load(&stop_test_index) + switch intrinsics.atomic_load(&stop_test_reason) { + case libc.SIGFPE: reason = .Arithmetic_Error + case libc.SIGILL: reason = .Illegal_Instruction + case libc.SIGSEGV: reason = .Segmentation_Fault + case SIGTRAP: reason = .Unhandled_Trap + } + ok = true + } + + return +} diff --git a/core/testing/signal_handler_other.odin b/core/testing/signal_handler_other.odin new file mode 100644 index 000000000..04981f5af --- /dev/null +++ b/core/testing/signal_handler_other.odin @@ -0,0 +1,19 @@ +//+private +//+build !windows !linux !darwin !freebsd !openbsd !netbsd !haiku +package testing + +_setup_signal_handler :: proc() { + // Do nothing. +} + +_setup_task_signal_handler :: proc(test_index: int) { + // Do nothing. +} + +_should_stop_runner :: proc() -> bool { + return false +} + +_should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) { + return 0, {}, false +} diff --git a/core/testing/testing.odin b/core/testing/testing.odin index a8c5ffa48..29fe853ef 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -1,10 +1,11 @@ package testing -import "core:fmt" -import "core:io" -import "core:time" import "base:intrinsics" +import "base:runtime" +import pkg_log "core:log" import "core:reflect" +import "core:sync/chan" +import "core:time" _ :: reflect // alias reflect to nothing to force visibility for -vet @@ -22,79 +23,107 @@ Internal_Test :: struct { Internal_Cleanup :: struct { procedure: proc(rawptr), user_data: rawptr, + ctx: runtime.Context, } T :: struct { error_count: int, - w: io.Writer, + // If your test needs to perform random operations, it's advised to use + // this value to seed a local random number generator rather than relying + // on the non-thread-safe global one. + // + // This way, your results will be deterministic. + // + // This value is chosen at startup of the test runner, logged, and may be + // specified by the user. It is the same for all tests of a single run. + seed: u64, + + channel: Update_Channel_Sender, cleanups: [dynamic]Internal_Cleanup, - _fail_now: proc() -> !, + // This allocator is shared between the test runner and its threads for + // cloning log strings, so they can outlive the lifetime of individual + // tests during channel transmission. + _log_allocator: runtime.Allocator, + + _fail_now_called: bool, } +@(deprecated="prefer `log.error`") error :: proc(t: ^T, args: ..any, loc := #caller_location) { - fmt.wprintf(t.w, "%v: ", loc) - fmt.wprintln(t.w, ..args) - t.error_count += 1 + pkg_log.error(..args, location = loc) } +@(deprecated="prefer `log.errorf`") errorf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { - fmt.wprintf(t.w, "%v: ", loc) - fmt.wprintf(t.w, format, ..args) - fmt.wprintln(t.w) - t.error_count += 1 + pkg_log.errorf(format, ..args, location = loc) } fail :: proc(t: ^T, loc := #caller_location) { - error(t, "FAIL", loc=loc) - t.error_count += 1 + pkg_log.error("FAIL", location=loc) } -fail_now :: proc(t: ^T, msg := "", loc := #caller_location) { +// fail_now will cause a test to immediately fail and abort, much in the same +// way a failed assertion or panic call will stop a thread. +// +// It is for when you absolutely need a test to fail without calling any of its +// deferred statements. It will be cleaner than a regular assert or panic, +// as the test runner will know to expect the signal this procedure will raise. +fail_now :: proc(t: ^T, msg := "", loc := #caller_location) -> ! { + t._fail_now_called = true if msg != "" { - error(t, "FAIL:", msg, loc=loc) + pkg_log.error("FAIL:", msg, location=loc) } else { - error(t, "FAIL", loc=loc) - } - t.error_count += 1 - if t._fail_now != nil { - t._fail_now() + pkg_log.error("FAIL", location=loc) } + runtime.trap() } failed :: proc(t: ^T) -> bool { return t.error_count != 0 } +@(deprecated="prefer `log.info`") log :: proc(t: ^T, args: ..any, loc := #caller_location) { - fmt.wprintln(t.w, ..args) + pkg_log.info(..args, location = loc) } +@(deprecated="prefer `log.infof`") logf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { - fmt.wprintf(t.w, format, ..args) - fmt.wprintln(t.w) + 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. +// 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. +// +// Each procedure will use a copy of the context at the time of registering, +// and if the test failed due to a timeout, failed assertion, panic, bounds-checking error, +// memory access violation, or any other signal-based fault, this procedure will +// run with greater privilege in the test runner's main thread. +// +// That means that any cleanup procedure absolutely must not fail in the same way, +// or it will take down the entire test runner with it. This is for when you +// need something to run no matter what, if a test failed. +// +// For almost every usual case, `defer` should be preferable and sufficient. cleanup :: proc(t: ^T, procedure: proc(rawptr), user_data: rawptr) { - append(&t.cleanups, Internal_Cleanup{procedure, user_data}) + append(&t.cleanups, Internal_Cleanup{procedure, user_data, context}) } expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bool { if !ok { - error(t, msg, loc=loc) + pkg_log.error(msg, location=loc) } return ok } expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_location) -> bool { if !ok { - errorf(t, format, ..args, loc=loc) + pkg_log.errorf(format, ..args, location=loc) } return ok } @@ -102,12 +131,15 @@ 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 { - errorf(t, "expected %v, got %v", expected, value, loc=loc) + pkg_log.errorf("expected %v, got %v", expected, value, location=loc) } return ok } set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) { - _fail_timeout(t, duration, loc) + chan.send(t.channel, Event_Set_Fail_Timeout { + at_time = time.time_add(time.now(), duration), + location = loc, + }) } diff --git a/core/text/edit/text_edit.odin b/core/text/edit/text_edit.odin index 6f21c9860..a4f8c06b9 100644 --- a/core/text/edit/text_edit.odin +++ b/core/text/edit/text_edit.odin @@ -183,16 +183,17 @@ undo_check :: proc(s: ^State) { } // insert text into the edit state - deletes the current selection -input_text :: proc(s: ^State, text: string) { +input_text :: proc(s: ^State, text: string) -> int { if len(text) == 0 { - return + return 0 } if has_selection(s) { selection_delete(s) } - insert(s, s.selection[0], text) - offset := s.selection[0] + len(text) + n := insert(s, s.selection[0], text) + offset := s.selection[0] + n s.selection = {offset, offset} + return n } // insert slice of runes into the edit state - deletes the current selection @@ -206,8 +207,11 @@ input_runes :: proc(s: ^State, text: []rune) { offset := s.selection[0] for r in text { b, w := utf8.encode_rune(r) - insert(s, offset, string(b[:w])) - offset += w + n := insert(s, offset, string(b[:w])) + offset += n + if n != w { + break + } } s.selection = {offset, offset} } @@ -219,17 +223,29 @@ input_rune :: proc(s: ^State, r: rune) { } offset := s.selection[0] b, w := utf8.encode_rune(r) - insert(s, offset, string(b[:w])) - offset += w + n := insert(s, offset, string(b[:w])) + offset += n s.selection = {offset, offset} } // insert a single rune into the edit state - deletes the current selection -insert :: proc(s: ^State, at: int, text: string) { +insert :: proc(s: ^State, at: int, text: string) -> int { undo_check(s) if s.builder != nil { - inject_at(&s.builder.buf, at, text) + if ok, _ := inject_at(&s.builder.buf, at, text); !ok { + n := cap(s.builder.buf) - len(s.builder.buf) + assert(n < len(text)) + for is_continuation_byte(text[n]) { + n -= 1 + } + if ok2, _ := inject_at(&s.builder.buf, at, text[:n]); !ok2 { + n = 0 + } + return n + } + return len(text) } + return 0 } // remove the wanted range withing, usually the selection within byte indices @@ -263,11 +279,12 @@ selection_delete :: proc(s: ^State) { s.selection = {lo, lo} } +is_continuation_byte :: proc(b: byte) -> bool { + return b >= 0x80 && b < 0xc0 +} + // translates the caret position translate_position :: proc(s: ^State, t: Translation) -> int { - is_continuation_byte :: proc(b: byte) -> bool { - return b >= 0x80 && b < 0xc0 - } is_space :: proc(b: byte) -> bool { return b == ' ' || b == '\t' || b == '\n' } diff --git a/core/text/i18n/doc.odin b/core/text/i18n/doc.odin index 54bf8b80f..d590fd123 100644 --- a/core/text/i18n/doc.odin +++ b/core/text/i18n/doc.odin @@ -1,31 +1,44 @@ /* -The `i18n` package is flexible and easy to use. +The `i18n` package is a flexible and easy to use way to localise applications. -It has one call to get a translation: `get`, which the user can alias into something like `T`. +It has two calls to get a translation: `get()` and `get_n()`, which the user can alias into something like `T` and `Tn` +with statements like: + T :: i18n.get + Tn :: i18n.get_n. -`get`, referred to as `T` here, has a few different signatures. -All of them will return the key if the entry can't be found in the active translation catalog. +`get()` is used for retrieving the translation of sentences which **never** change in form, +like for instance "Connection established" or "All temporary files have been deleted". +Note that the number (singular, dual, plural, whatever else) is not relevant: the sentence is fixed and it will have only one possible translation in any other language. -- `T(key)` returns the translation of `key`. -- `T(key, n)` returns a pluralized translation of `key` according to value `n`. +`get_n()` is used for retrieving the translations of sentences which change according to the number of items referenced. +The various signatures of `get_n()` have one more parameter, `n`, which will receive that number and be used +to select the correct form according to the pluralizer attached to the message catalogue when initially loaded; +for instance, to summarise a rather complex matter, some languages use the singular form when referring to 0 items and some use the (only in their case) plural forms; +also, languages may have more or less quantifier forms than a single singular form and a universal plural form: +for instance, Chinese has just one form for any quantity, while Welsh may have up to 6 different forms for specific different quantities. -- `T(section, key)` returns the translation of `key` in `section`. -- `T(section, key, n)` returns a pluralized translation of `key` in `section` according to value `n`. +Both `get()` and `get_n()`, referred to as `T` and `Tn` here, have several different signatures. +All of them will return the key if the entry can't be found in the active translation catalogue. +By default lookup take place in the global `i18n.ACTIVE` catalogue for ease of use, unless a specific catalogue is supplied. -By default lookup take place in the global `i18n.ACTIVE` catalog for ease of use. -If you want to override which translation to use, for example in a language preview dialog, you can use the following: +- `T(key)` returns the translation of `key`. +- `T(key, catalog)` returns the translation of `key` from explictly supplied catalogue. +- `T(section, key)` returns the translation of `key` in `section`. +- `T(section, key, catalog)` returns the translation of `key` in `section` from explictly supplied catalogue. -- `T(key, n, catalog)` returns the pluralized version of `key` from explictly supplied catalog. -- `T(section, key, n, catalog)` returns the pluralized version of `key` in `section` from explictly supplied catalog. +- `Tn(key, n)` returns the translation of `key` according to number of items `n`. +- `Tn(key, n, catalog)` returns the translation of `key` from explictly supplied catalogue. +- `Tn(section, key, n)` returns the translation of `key` in `section` according to number of items `n`. +- `Tn(section, key, n, catalog)` returns the translation of `key` in `section` according to number of items `n` from explictly supplied catalogue. If a catalog has translation contexts or sections, then omitting it in the above calls looks up in section "". -The default pluralization rule is n != 1, which is to say that passing n == 1 (or not passing n) returns the singular form. -Passing n != 1 returns plural form 1. +The default pluralization rule is `n != 1`, which is to say that passing `n == 1` returns the singular form (in slot 0). +Passing `n != 1` returns the plural form in slot 1 (if any). Should a language not conform to this rule, you can pass a pluralizer procedure to the catalog parser. -This is a procedure that maps an integer to an integer, taking a value and returning which plural slot should be used. +This is a procedure that maps an integer to an integer, taking a quantity and returning which plural slot should be used. You can also assign it to a loaded catalog after parsing, of course. @@ -34,24 +47,21 @@ Example: import "core:fmt" import "core:text/i18n" - T :: i18n.get + T :: i18n.get + Tn :: i18n.get_n mo :: proc() { using fmt err: i18n.Error - /* - Parse MO file and set it as the active translation so we can omit `get`'s "catalog" parameter. - */ + // Parse MO file and set it as the active translation so we can omit `get`'s "catalog" parameter. i18n.ACTIVE, err = i18n.parse_mo(#load("translations/nl_NL.mo")) defer i18n.destroy() if err != .None { return } - /* - These are in the .MO catalog. - */ + // These are in the .MO catalog. println("-----") println(T("")) println("-----") @@ -60,13 +70,11 @@ Example: println(T("Hellope, World!")) println("-----") // We pass 1 into `T` to get the singular format string, then 1 again into printf. - printf(T("There is %d leaf.\n", 1), 1) + printf(Tn("There is %d leaf.\n", 1), 1) // We pass 42 into `T` to get the plural format string, then 42 again into printf. - printf(T("There is %d leaf.\n", 42), 42) + printf(Tn("There is %d leaf.\n", 42), 42) - /* - This isn't in the translation catalog, so the key is passed back untranslated. - */ + // This isn't in the translation catalog, so the key is passed back untranslated. println("-----") println(T("Come visit us on Discord!")) } @@ -76,19 +84,13 @@ Example: err: i18n.Error - /* - Parse QT file and set it as the active translation so we can omit `get`'s "catalog" parameter. - */ + // Parse QT file and set it as the active translation so we can omit `get`'s "catalog" parameter. i18n.ACTIVE, err = i18n.parse_qt(#load("translations/nl_NL-qt-ts.ts")) defer i18n.destroy() - if err != .None { - return - } + if err != .None { return } - /* - These are in the .TS catalog. As you can see they have sections. - */ + // These are in the .TS catalog. As you can see they have sections. println("--- Page section ---") println("Page:Text for translation =", T("Page", "Text for translation")) println("-----") @@ -99,8 +101,8 @@ Example: println("-----") println("--- apple_count section ---") println("apple_count:%d apple(s) =") - println("\t 1 =", T("apple_count", "%d apple(s)", 1)) - println("\t 42 =", T("apple_count", "%d apple(s)", 42)) + println("\t 1 =", Tn("apple_count", "%d apple(s)", 1)) + println("\t 42 =", Tn("apple_count", "%d apple(s)", 42)) } */ package i18n diff --git a/core/text/i18n/gettext.odin b/core/text/i18n/gettext.odin index d5537a19c..3ac9109ef 100644 --- a/core/text/i18n/gettext.odin +++ b/core/text/i18n/gettext.odin @@ -60,10 +60,6 @@ parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, plur translation.pluralize = pluralizer strings.intern_init(&translation.intern, allocator, allocator) - // Gettext MO files only have one section. - translation.k_v[""] = {} - section := &translation.k_v[""] - for n := u32(0); n < count; n += 1 { /* Grab string's original length and offset. @@ -83,37 +79,60 @@ parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, plur max_offset := int(max(o_offset + o_length + 1, t_offset + t_length + 1)) if len(data) < max_offset { return translation, .Premature_EOF } - key := data[o_offset:][:o_length] - val := data[t_offset:][:t_length] + key_data := data[o_offset:][:o_length] + val_data := data[t_offset:][:t_length] /* Could be a pluralized string. */ zero := []byte{0} + keys := bytes.split(key_data, zero); defer delete(keys) + vals := bytes.split(val_data, zero); defer delete(vals) - keys := bytes.split(key, zero) - vals := bytes.split(val, zero) - - if len(keys) != len(vals) || max(len(keys), len(vals)) > MAX_PLURALS { + if (len(keys) != 1 && len(keys) != 2) || len(vals) > MAX_PLURALS { return translation, .MO_File_Incorrect_Plural_Count } for k in keys { - interned_key, _ := strings.intern_get(&translation.intern, string(k)) + section_name := "" + key := string(k) - interned_vals := make([]string, len(keys)) + // Scan for EOT + for ch, i in k { + if ch == 0x04 { + section_name = string(k[:i]) + key = string(k[i+1:]) + break + } + } + + // If we merge sections, then all entries end in the "" context. + if options.merge_sections { + section_name = "" + } + + section_name, _ = strings.intern_get(&translation.intern, section_name) + if section_name not_in translation.k_v { + translation.k_v[section_name] = {} + } + + section := &translation.k_v[section_name] + interned_key, _ := strings.intern_get(&translation.intern, string(key)) + + // Duplicate key should not be allowed. + if interned_key in section { + return translation, .Duplicate_Key + } + + interned_vals := make([]string, len(vals)) last_val: string - i := 0 - for v in vals { + for v, i in vals { interned_vals[i], _ = strings.intern_get(&translation.intern, string(v)) last_val = interned_vals[i] - i += 1 } section[interned_key] = interned_vals } - delete(vals) - delete(keys) } return } diff --git a/core/text/i18n/i18n.odin b/core/text/i18n/i18n.odin index 64593c4e8..0190ef0f7 100644 --- a/core/text/i18n/i18n.odin +++ b/core/text/i18n/i18n.odin @@ -10,23 +10,13 @@ package i18n */ import "core:strings" -/* - TODO: - - Support for more translation catalog file formats. -*/ - -/* - Currently active catalog. -*/ +// Currently active catalog. ACTIVE: ^Translation // Allow between 1 and 255 plural forms. Default: 10. MAX_PLURALS :: min(max(#config(ODIN_i18N_MAX_PLURAL_FORMS, 10), 1), 255) -/* - The main data structure. This can be generated from various different file formats, as long as we have a parser for them. -*/ - +// The main data structure. This can be generated from various different file formats, as long as we have a parser for them. Section :: map[string][]string Translation :: struct { @@ -37,34 +27,24 @@ Translation :: struct { } Error :: enum { - /* - General return values. - */ + // General return values. None = 0, Empty_Translation_Catalog, Duplicate_Key, - /* - Couldn't find, open or read file. - */ + // Couldn't find, open or read file. File_Error, - /* - File too short. - */ + // File too short. Premature_EOF, - /* - GNU Gettext *.MO file errors. - */ + // GNU Gettext *.MO file errors. MO_File_Invalid_Signature, MO_File_Unsupported_Version, MO_File_Invalid, MO_File_Incorrect_Plural_Count, - /* - Qt Linguist *.TS file errors. - */ + // Qt Linguist *.TS file errors. TS_File_Parse_Error, TS_File_Expected_Context, TS_File_Expected_Context_Name, @@ -85,73 +65,142 @@ DEFAULT_PARSE_OPTIONS :: Parse_Options{ } /* - Several ways to use: - - get(key), which defaults to the singular form and i18n.ACTIVE catalog, or - - get(key, number), which returns the appropriate plural from the active catalog, or - - get(key, number, catalog) to grab text from a specific one. -*/ -get_single_section :: proc(key: string, number := 1, catalog: ^Translation = ACTIVE) -> (value: string) { - /* - A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule. - */ - plural := 1 if number != 1 else 0 + Returns the first translation string for the passed `key`. + It is also aliased with `get()`. - if catalog.pluralize != nil { - plural = catalog.pluralize(number) - } - return get_by_slot(key, plural, catalog) + Two ways to use it: + - get(key), which defaults to the `i18n.ACTIVE` catalogue, or + - get(key, catalog) to grab text from a specific loaded catalogue + + Inputs: + - key: the string to translate + - catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE) + + Returns: the translated string, or the original `key` if no translation was found. +*/ +get_single_section :: proc(key: string, catalog: ^Translation = ACTIVE) -> (value: string) { + return get_by_slot(key, 0, catalog) } /* - Several ways to use: - - get(section, key), which defaults to the singular form and i18n.ACTIVE catalog, or - - get(section, key, number), which returns the appropriate plural from the active catalog, or - - get(section, key, number, catalog) to grab text from a specific one. -*/ -get_by_section :: proc(section, key: string, number := 1, catalog: ^Translation = ACTIVE) -> (value: string) { - /* - A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule. - */ - plural := 1 if number != 1 else 0 + Returns the first translation string for the passed `key` in a specific section or context. + It is also aliases with `get()`. - if catalog.pluralize != nil { - plural = catalog.pluralize(number) - } - return get_by_slot(section, key, plural, catalog) + Two ways to use it: + - get(section, key), which defaults to the `i18n.ACTIVE` catalogue, or + - get(section, key, catalog) to grab text from a specific loaded catalogue + + Inputs: + - section: the catalogue section (sometimes also called 'context') in which to look up the translation + - key: the string to translate + - catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE) + + Returns: the translated string, or the original `key` if no translation was found. +*/ +get_by_section :: proc(section, key: string, catalog: ^Translation = ACTIVE) -> (value: string) { + return get_by_slot(section, key, 0, catalog) } + get :: proc{get_single_section, get_by_section} /* - Several ways to use: - - get_by_slot(key), which defaults to the singular form and i18n.ACTIVE catalog, or - - get_by_slot(key, slot), which returns the requested plural from the active catalog, or - - get_by_slot(key, slot, catalog) to grab text from a specific one. + Returns the translation string for the passed `key` in a specific plural form (if present in the catalogue). + It is also aliased with `get_n()`. + + Two ways to use it: + - get_n(key, quantity), which returns the appropriate plural from the active catalogue, or + - get_n(key, quantity, catalog) to grab text from a specific loaded catalogue + + Inputs: + - key: the string to translate + - quantity: the quantity of item to be used to select the correct plural form + - catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE) + + Returns: the translated string, or the original `key` if no translation was found. +*/ +get_single_section_with_quantity :: proc(key: string, quantity: int, catalog: ^Translation = ACTIVE) -> (value: string) { + /* + A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule. + */ + slot := 1 if quantity != 1 else 0 + + if catalog.pluralize != nil { + slot = catalog.pluralize(quantity) + } + return get_by_slot(key, slot, catalog) +} + +/* + Returns the translation string for the passed `key` in a specific plural form (if present in the catalogue) + in a specific section or context. + It is also aliases with `get_n()`. + + Two ways to use it: + - get(section, key, quantity), which returns the appropriate plural from the active catalogue, or + - get(section, key, quantity, catalog) to grab text from a specific loaded catalogue + + Inputs: + - section: the catalogue section (sometime also called 'context') from which to lookup the translation + - key: the string to translate + - qantity: the quantity of item to be used to select the correct plural form + - catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE) + + Returns: the translated string, or the original `key` if no translation was found +*/ +get_by_section_with_quantity :: proc(section, key: string, quantity: int, catalog: ^Translation = ACTIVE) -> (value: string) { + /* + A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule. + */ + slot := 1 if quantity != 1 else 0 + + if catalog.pluralize != nil { + slot = catalog.pluralize(quantity) + } + return get_by_slot(section, key, slot, catalog) +} +get_n :: proc{get_single_section_with_quantity, get_by_section_with_quantity} + +/* + Two ways to use: + - get_by_slot(key, slot), which returns the requested plural from the active catalogue, or + - get_by_slot(key, slot, catalog) to grab text from a specific loaded catalogue. If a file format parser doesn't (yet) support plural slots, each of the slots will point at the same string. + - section: the catalogue section (sometime also called 'context') from which to lookup the translation + + Inputs: + - key: the string to translate. + - slot: the translation slot to choose (slots refer to plural forms specific for each language and their meaning changes from catalogue to catalogue). + - catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE) + + Returns: the translated string, or the original `key` if no translation was found. */ -get_by_slot_single_section :: proc(key: string, slot := 0, catalog: ^Translation = ACTIVE) -> (value: string) { +get_by_slot_single_section :: proc(key: string, slot: int, catalog: ^Translation = ACTIVE) -> (value: string) { return get_by_slot_by_section("", key, slot, catalog) } /* - Several ways to use: - - get_by_slot(key), which defaults to the singular form and i18n.ACTIVE catalog, or + Two ways to use: - get_by_slot(key, slot), which returns the requested plural from the active catalog, or - get_by_slot(key, slot, catalog) to grab text from a specific one. If a file format parser doesn't (yet) support plural slots, each of the slots will point at the same string. + + Inputs: + - section: the catalogue section (sometime also called 'context') from which to lookup the translation + - key: the string to translate. + - slot: the translation slot to choose (slots refer to plural forms specific for each language and their meaning changes from catalogue to catalogue). + - catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE) + + Returns: the translated string or the original `key` if no translation was found. */ -get_by_slot_by_section :: proc(section, key: string, slot := 0, catalog: ^Translation = ACTIVE) -> (value: string) { +get_by_slot_by_section :: proc(section, key: string, slot: int, catalog: ^Translation = ACTIVE) -> (value: string) { if catalog == nil || section not_in catalog.k_v { - /* - Return the key if the catalog catalog hasn't been initialized yet, or the section is not present. - */ + // Return the key if the catalog catalog hasn't been initialized yet, or the section is not present. return key } - /* - Return the translation from the requested slot if this key is known, else return the key. - */ + // Return the translation from the requested slot if this key is known, else return the key. if translations, ok := catalog.k_v[section][key]; ok { plural := min(max(0, slot), len(catalog.k_v[section][key]) - 1) return translations[plural] @@ -161,7 +210,6 @@ get_by_slot_by_section :: proc(section, key: string, slot := 0, catalog: ^Transl get_by_slot :: proc{get_by_slot_single_section, get_by_slot_by_section} /* - Same for destroy: - destroy(), to clean up the currently active catalog catalog i18n.ACTIVE - destroy(catalog), to clean up a specific catalog. */ diff --git a/core/text/i18n/qt_linguist.odin b/core/text/i18n/qt_linguist.odin index 0e75df873..bdd3f5fd7 100644 --- a/core/text/i18n/qt_linguist.odin +++ b/core/text/i18n/qt_linguist.odin @@ -162,8 +162,6 @@ parse_qt_linguist_file :: proc(filename: string, options := DEFAULT_PARSE_OPTION context.allocator = allocator data, data_ok := os.read_entire_file(filename) - defer delete(data) - if !data_ok { return {}, .File_Error } return parse_qt_linguist_from_bytes(data, options, pluralizer, allocator) diff --git a/core/text/scanner/scanner.odin b/core/text/scanner/scanner.odin index 7c17a0ec0..d27c66f24 100644 --- a/core/text/scanner/scanner.odin +++ b/core/text/scanner/scanner.odin @@ -8,6 +8,7 @@ // A Scanner may be customized to recognize only a subset of those literals and to recognize different identifiers and white space characters. package text_scanner +import "base:runtime" import "core:fmt" import "core:strings" import "core:unicode" @@ -23,10 +24,12 @@ Position :: struct { } // position_is_valid reports where the position is valid +@(require_results) position_is_valid :: proc(pos: Position) -> bool { return pos.line > 0 } +@(require_results) position_to_string :: proc(pos: Position, allocator := context.temp_allocator) -> string { s := pos.filename if s == "" { @@ -140,7 +143,7 @@ init :: proc(s: ^Scanner, src: string, filename := "") -> ^Scanner { } -@(private) +@(private, require_results) advance :: proc(s: ^Scanner) -> rune { if s.src_pos >= len(s.src) { s.prev_char_len = 0 @@ -190,6 +193,7 @@ next :: proc(s: ^Scanner) -> rune { // peek returns the next Unicode character in the source without advancing the scanner // It returns EOF if the scanner's position is at least the last character of the source // if n > 0, it call next n times and return the nth Unicode character and then restore the Scanner's state +@(require_results) peek :: proc(s: ^Scanner, n := 0) -> (ch: rune) { if s.ch == -2 { s.ch = advance(s) @@ -211,6 +215,7 @@ peek :: proc(s: ^Scanner, n := 0) -> (ch: rune) { // peek returns the next token in the source // It returns EOF if the scanner's position is at least the last character of the source // if n > 0, it call next n times and return the nth token and then restore the Scanner's state +@(require_results) peek_token :: proc(s: ^Scanner, n := 0) -> (tok: rune) { assert(n >= 0) prev_s := s^ @@ -249,7 +254,7 @@ errorf :: proc(s: ^Scanner, format: string, args: ..any) { error(s, fmt.tprintf(format, ..args)) } -@(private) +@(private, require_results) is_ident_rune :: proc(s: ^Scanner, ch: rune, i: int) -> bool { if s.is_ident_rune != nil { return s.is_ident_rune(ch, i) @@ -257,7 +262,7 @@ is_ident_rune :: proc(s: ^Scanner, ch: rune, i: int) -> bool { return ch == '_' || unicode.is_letter(ch) || unicode.is_digit(ch) && i > 0 } -@(private) +@(private, require_results) scan_identifier :: proc(s: ^Scanner) -> rune { ch := advance(s) for i := 1; is_ident_rune(s, ch, i); i += 1 { @@ -266,13 +271,13 @@ scan_identifier :: proc(s: ^Scanner) -> rune { return ch } -@(private) lower :: proc(ch: rune) -> rune { return ('a' - 'A') | ch } -@(private) is_decimal :: proc(ch: rune) -> bool { return '0' <= ch && ch <= '9' } -@(private) is_hex :: proc(ch: rune) -> bool { return '0' <= ch && ch <= '9' || 'a' <= lower(ch) && lower(ch) <= 'f' } +@(private, require_results) lower :: proc(ch: rune) -> rune { return ('a' - 'A') | ch } +@(private, require_results) is_decimal :: proc(ch: rune) -> bool { return '0' <= ch && ch <= '9' } +@(private, require_results) is_hex :: proc(ch: rune) -> bool { return '0' <= ch && ch <= '9' || 'a' <= lower(ch) && lower(ch) <= 'f' } -@(private) +@(private, require_results) scan_number :: proc(s: ^Scanner, ch: rune, seen_dot: bool) -> (rune, rune) { lit_name :: proc(prefix: rune) -> string { switch prefix { @@ -417,7 +422,7 @@ scan_number :: proc(s: ^Scanner, ch: rune, seen_dot: bool) -> (rune, rune) { return tok, ch } -@(private) +@(private, require_results) scan_string :: proc(s: ^Scanner, quote: rune) -> (n: int) { digit_val :: proc(ch: rune) -> int { switch v := lower(ch); v { @@ -484,7 +489,7 @@ scan_char :: proc(s: ^Scanner) { } } -@(private) +@(private, require_results) scan_comment :: proc(s: ^Scanner, ch: rune) -> rune { ch := ch if ch == '/' { // line comment @@ -563,13 +568,13 @@ scan :: proc(s: ^Scanner) -> (tok: rune) { break case '"': if .Scan_Strings in s.flags { - scan_string(s, '"') + _ = scan_string(s, '"') tok = String } ch = advance(s) case '\'': if .Scan_Chars in s.flags { - scan_string(s, '\'') + _ = scan_string(s, '\'') tok = Char } ch = advance(s) @@ -611,6 +616,7 @@ scan :: proc(s: ^Scanner) -> (tok: rune) { // position returns the position of the character immediately after the character or token returns by the previous call to next or scan // Use the Scanner's position field for the most recently scanned token position +@(require_results) position :: proc(s: ^Scanner) -> Position { pos: Position pos.filename = s.pos.filename @@ -630,6 +636,7 @@ position :: proc(s: ^Scanner) -> Position { } // token_text returns the string of the most recently scanned token +@(require_results) token_text :: proc(s: ^Scanner) -> string { if s.tok_pos < 0 { return "" @@ -639,7 +646,8 @@ token_text :: proc(s: ^Scanner) -> string { // token_string returns a printable string for a token or Unicode character // By default, it uses the context.temp_allocator to produce the string -token_string :: proc(tok: rune, allocator := context.temp_allocator) -> string { +@(require_results) +token_string :: proc(tok: rune, allocator: runtime.Allocator) -> string { context.allocator = allocator switch tok { case EOF: return strings.clone("EOF") diff --git a/core/text/table/doc.odin b/core/text/table/doc.odin index 76886bdea..955ab8d21 100644 --- a/core/text/table/doc.odin +++ b/core/text/table/doc.odin @@ -1,18 +1,27 @@ /* -The package `table` implements ASCII/markdown/HTML/custom rendering of tables. +The package `table` implements plain-text/markdown/HTML/custom rendering of tables. **Custom rendering example:** - tbl := init(&Table{}) - padding(tbl, 0, 1) - row(tbl, "A_LONG_ENUM", "= 54,", "// A comment about A_LONG_ENUM") - row(tbl, "AN_EVEN_LONGER_ENUM", "= 1,", "// A comment about AN_EVEN_LONGER_ENUM") - build(tbl) - for row in 0.. (result: int) { + for r in str { + result += 2 + } + return + } + + table.write_plain_table(stdout, tbl, simple_cjk_width_proc) + +This procedure will output 2 times the number of UTF-8 runes in a string, a +simple heuristic for CJK-only wide text. + +**Unicode Support:** + +This package makes use of the `grapheme_count` procedure from the +`core:unicode/utf8` package. It is a complete, standards-compliant +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: + + package main + + import "core:fmt" + import "core:io" + import "core:os" + import "core:text/table" + + scripts :: proc(w: io.Writer) { + t: table.Table + table.init(&t) + table.caption(&t, "Tést Suite") + table.padding(&t, 1, 3) + table.header_of_aligned_values(&t, {{.Left, "Script"}, {.Center, "Sample"}}) + + table.row(&t, "Latin", "At vero eos et accusamus et iusto odio dignissimos ducimus,") + table.row(&t, "Cyrillic", "Ру́сский язы́к — язык восточнославянской группы славянской") + table.row(&t, "Greek", "Η ελληνική γλώσσα ανήκει στην ινδοευρωπαϊκή οικογένεια") + table.row(&t, "Younger Futhark", "ᚴᚢᚱᛘᛦ ᚴᚢᚾᚢᚴᛦ ᚴᛅᚱᚦᛁ ᚴᚢᛒᛚ ᚦᚢᛋᛁ ᛅᚠᛏ ᚦᚢᚱᚢᛁ ᚴᚢᚾᚢ ᛋᛁᚾᛅ ᛏᛅᚾᛘᛅᚱᚴᛅᛦ ᛒᚢᛏ") + table.row(&t, "Chinese hanzi", "官話為汉语的一支,主體分布在中国北部和西南部的大部分地区。") + table.row(&t, "Japanese kana", "いろはにほへとちりぬるをわかよたれそつねならむ") + table.row(&t, "Korean hangul", "한글, 조선글은 한국어의 공식문자로서, 세종이 한국어를") + table.row(&t, "Thai", "ภาษาไทย หรือ ภาษาไทยกลาง เป็นภาษาในกลุ่มภาษาไท ซึ่งเป็นกลุ่มย่อยของตระกูลภาษาขร้า-ไท") + table.row(&t, "Georgian", "ქართული ენა — ქართველურ ენათა ოჯახის ენა. ქართველების მშობლიური ენა,") + table.row(&t, "Armenian", "Իր շուրջ հինգհազարամյա գոյության ընթացքում հայերենը շփվել է տարբեր") + table.row(&t) + table.row_of_aligned_values(&t, {{.Left, "Arabic"}, {.Right, "ٱللُّغَةُ ٱلْعَرَبِيَّة هي أكثر اللغات السامية تحدثًا، وإحدى أكثر"}}) + table.row_of_aligned_values(&t, {{.Left, "Hebrew"}, {.Right, "עִבְרִית היא שפה שמית, ממשפחת השפות האפרו-אסייתיות, הידועה"}}) + table.row(&t) + table.row(&t, "Swedish", "Växjö [ˈvɛkːˌɧøː] är en tätort i södra Smålands inland samt centralort i Växjö kommun") + table.row(&t, "Saxon", "Hwæt! We Gardena in geardagum, þeodcyninga, þrym gefrunon, hu ða æþelingas ellen fremedon.") + table.row(&t) + table.aligned_row_of_values(&t, .Center, "Emoji (Single codepoints)", "\U0001f4ae \U0001F600 \U0001F201 \U0001F21A") + table.row(&t, "Excessive Diacritics", "H̷e̶l̵l̸o̴p̵e̷ ̸w̶o̸r̵l̶d̵!̴") + + table.write_plain_table(w, &t) + fmt.println() + } + + main :: proc() { + stdout := os.stream_from_handle(os.stdout) + + scripts(stdout) + } + +This will print out: + + +----------------------------------------------------------------------------------------------------------------------------+ + | Tést Suite | + +-----------------------------+----------------------------------------------------------------------------------------------+ + | Script | Sample | + +-----------------------------+----------------------------------------------------------------------------------------------+ + | Latin | At vero eos et accusamus et iusto odio dignissimos ducimus, | + | Cyrillic | Ру́сский язы́к — язык восточнославянской группы славянской | + | Greek | Η ελληνική γλώσσα ανήκει στην ινδοευρωπαϊκή οικογένεια | + | Younger Futhark | ᚴᚢᚱᛘᛦ ᚴᚢᚾᚢᚴᛦ ᚴᛅᚱᚦᛁ ᚴᚢᛒᛚ ᚦᚢᛋᛁ ᛅᚠᛏ ᚦᚢᚱᚢᛁ ᚴᚢᚾᚢ ᛋᛁᚾᛅ ᛏᛅᚾᛘᛅᚱᚴᛅᛦ ᛒᚢᛏ | + | Chinese hanzi | 官話為汉语的一支,主體分布在中国北部和西南部的大部分地区。 | + | Japanese kana | いろはにほへとちりぬるをわかよたれそつねならむ | + | Korean hangul | 한글, 조선글은 한국어의 공식문자로서, 세종이 한국어를 | + | Thai | ภาษาไทย หรือ ภาษาไทยกลาง เป็นภาษาในกลุ่มภาษาไท ซึ่งเป็นกลุ่มย่อยของตระกูลภาษาขร้า-ไท | + | Georgian | ქართული ენა — ქართველურ ენათა ოჯახის ენა. ქართველების მშობლიური ენა, | + | Armenian | Իր շուրջ հինգհազարամյա գոյության ընթացքում հայերենը շփվել է տարբեր | + | | | + | Arabic | ٱللُّغَةُ ٱلْعَرَبِيَّة هي أكثر اللغات السامية تحدثًا، وإحدى أكثر | + | Hebrew | עִבְרִית היא שפה שמית, ממשפחת השפות האפרו-אסייתיות, הידועה | + | | | + | Swedish | Växjö [ˈvɛkːˌɧøː] är en tätort i södra Smålands inland samt centralort i Växjö kommun | + | Saxon | Hwæt! We Gardena in geardagum, þeodcyninga, þrym gefrunon, hu ða æþelingas ellen fremedon. | + | | | + | Emoji (Single codepoints) | 💮 😀 🈁 🈚 | + | Excessive Diacritics | H̷e̶l̵l̸o̴p̵e̷ ̸w̶o̸r̵l̶d̵!̴ | + +-----------------------------+----------------------------------------------------------------------------------------------+ + +**Decorated Tables:** + +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: + + package main + + import "core:fmt" + import "core:io" + import "core:os" + import "core:text/table" + + box_drawing :: proc(w: io.Writer) { + t: table.Table + table.init(&t) + table.caption(&t, "Box Drawing Example") + table.padding(&t, 2, 2) + table.header_of_aligned_values(&t, {{.Left, "Operating System"}, {.Center, "Year Introduced"}}) + + table.row(&t, "UNIX", "1973") + table.row(&t, "MS-DOS", "1981") + table.row(&t, "Commodore 64 KERNAL", "1982") + table.row(&t, "Mac OS", "1984") + table.row(&t, "Amiga", "1985") + table.row(&t, "Windows 1.0", "1985") + table.row(&t, "Linux", "1991") + table.row(&t, "Windows 3.1", "1992") + + decorations := table.Decorations { + "┌", "┬", "┐", + "├", "┼", "┤", + "└", "┴", "┘", + "│", "─", + } + + table.write_decorated_table(w, &t, decorations) + fmt.println() + } + + main :: proc() { + stdout := os.stream_from_handle(os.stdout) + + box_drawing(stdout) + } + +While the decorations support multi-codepoint Unicode graphemes, do note that +each border character should not be larger than one monospace cell. + */ package text_table diff --git a/core/text/table/table.odin b/core/text/table/table.odin index 5423519d3..27c99b1f1 100644 --- a/core/text/table/table.odin +++ b/core/text/table/table.odin @@ -4,18 +4,22 @@ List of contributors: oskarnp: Initial implementation. + Feoramund: Unicode support. */ package text_table import "core:io" import "core:fmt" +import "core:log" import "core:mem" import "core:mem/virtual" +import "core:unicode/utf8" import "base:runtime" Cell :: struct { text: string, + width: int, alignment: Cell_Alignment, } @@ -25,6 +29,14 @@ Cell_Alignment :: enum { Right, } +Aligned_Value :: struct { + alignment: Cell_Alignment, + value: any, +} + +// Determines the width of a string used in the table for alignment purposes. +Width_Proc :: #type proc(str: string) -> int + Table :: struct { lpad, rpad: int, // Cell padding (left/right) cells: [dynamic]Cell, @@ -34,13 +46,33 @@ Table :: struct { table_allocator: runtime.Allocator, // Used for allocating cells/colw format_allocator: runtime.Allocator, // Used for allocating Cell.text when applicable - dirty: bool, // True if build() needs to be called before rendering - // The following are computed on build() - colw: [dynamic]int, // Width of each column (including padding, excluding borders) + colw: [dynamic]int, // Width of each column (excluding padding and borders) tblw: int, // Width of entire table (including padding, excluding borders) } +Decorations :: struct { + // These are strings, because of multi-codepoint Unicode graphemes. + + // Connecting decorations: + nw, n, ne, + w, x, e, + sw, s, se: string, + + // Straight lines: + vert: string, + horz: string, +} + +ascii_width_proc :: proc(str: string) -> int { + return len(str) +} + +unicode_width_proc :: proc(str: string) -> (width: int) { + _, _, width = #force_inline utf8.grapheme_count(str) + return +} + init :: proc{init_with_allocator, init_with_virtual_arena, init_with_mem_arena} init_with_allocator :: proc(tbl: ^Table, format_allocator := context.temp_allocator, table_allocator := context.allocator) -> ^Table { @@ -65,13 +97,11 @@ destroy :: proc(tbl: ^Table) { caption :: proc(tbl: ^Table, value: string) { tbl.caption = value - tbl.dirty = true } padding :: proc(tbl: ^Table, lpad, rpad: int) { tbl.lpad = lpad tbl.rpad = rpad - tbl.dirty = true } get_cell :: proc(tbl: ^Table, row, col: int, loc := #caller_location) -> ^Cell { @@ -81,43 +111,47 @@ get_cell :: proc(tbl: ^Table, row, col: int, loc := #caller_location) -> ^Cell { return &tbl.cells[row*tbl.nr_cols + col] } -set_cell_value_and_alignment :: proc(tbl: ^Table, row, col: int, value: string, alignment: Cell_Alignment) { - cell := get_cell(tbl, row, col) - cell.text = format(tbl, "%v", value) - cell.alignment = alignment - tbl.dirty = true +@private +to_string :: #force_inline proc(tbl: ^Table, value: any, loc := #caller_location) -> (result: string) { + switch val in value { + case nil: + result = "" + case string: + result = val + case cstring: + result = cast(string)val + case: + result = format(tbl, "%v", val) + if result == "" { + log.error("text/table.format() resulted in empty string (arena out of memory?)", location = loc) + } + } + return } set_cell_value :: proc(tbl: ^Table, row, col: int, value: any, loc := #caller_location) { cell := get_cell(tbl, row, col, loc) - switch val in value { - case nil: - cell.text = "" - case string: - cell.text = string(val) - case cstring: - cell.text = string(val) - case: - cell.text = format(tbl, "%v", val) - if cell.text == "" { - fmt.eprintf("{} text/table: format() resulted in empty string (arena out of memory?)\n", loc) - } - } - tbl.dirty = true + cell.text = to_string(tbl, value, loc) } set_cell_alignment :: proc(tbl: ^Table, row, col: int, alignment: Cell_Alignment, loc := #caller_location) { cell := get_cell(tbl, row, col, loc) cell.alignment = alignment - tbl.dirty = true } -format :: proc(tbl: ^Table, _fmt: string, args: ..any, loc := #caller_location) -> string { +set_cell_value_and_alignment :: proc(tbl: ^Table, row, col: int, value: any, alignment: Cell_Alignment, loc := #caller_location) { + cell := get_cell(tbl, row, col, loc) + cell.text = to_string(tbl, value, loc) + cell.alignment = alignment +} + +format :: proc(tbl: ^Table, _fmt: string, args: ..any) -> string { context.allocator = tbl.format_allocator return fmt.aprintf(_fmt, ..args) } -header :: proc(tbl: ^Table, values: ..any, loc := #caller_location) { +header :: header_of_values +header_of_values :: proc(tbl: ^Table, values: ..any, loc := #caller_location) { if (tbl.has_header_row && tbl.nr_rows != 1) || (!tbl.has_header_row && tbl.nr_rows != 0) { panic("Cannot add headers after rows have been added", loc) } @@ -133,26 +167,108 @@ header :: proc(tbl: ^Table, values: ..any, loc := #caller_location) { set_cell_value(tbl, header_row(tbl), col, val, loc) col += 1 } - - tbl.dirty = true } -row :: proc(tbl: ^Table, values: ..any, loc := #caller_location) { +aligned_header_of_values :: proc(tbl: ^Table, alignment: Cell_Alignment, values: ..any, loc := #caller_location) { + if (tbl.has_header_row && tbl.nr_rows != 1) || (!tbl.has_header_row && tbl.nr_rows != 0) { + panic("Cannot add headers after rows have been added", loc) + } + + if tbl.nr_rows == 0 { + tbl.nr_rows += 1 + tbl.has_header_row = true + } + + col := tbl.nr_cols + tbl.nr_cols += len(values) + for val in values { + set_cell_value_and_alignment(tbl, header_row(tbl), col, val, alignment, loc) + col += 1 + } +} + +header_of_aligned_values :: proc(tbl: ^Table, aligned_values: []Aligned_Value, loc := #caller_location) { + if (tbl.has_header_row && tbl.nr_rows != 1) || (!tbl.has_header_row && tbl.nr_rows != 0) { + panic("Cannot add headers after rows have been added", loc) + } + + if tbl.nr_rows == 0 { + tbl.nr_rows += 1 + tbl.has_header_row = true + } + + col := tbl.nr_cols + tbl.nr_cols += len(aligned_values) + for av in aligned_values { + set_cell_value_and_alignment(tbl, header_row(tbl), col, av.value, av.alignment, loc) + col += 1 + } +} + +row :: row_of_values +row_of_values :: proc(tbl: ^Table, values: ..any, loc := #caller_location) { if tbl.nr_cols == 0 { if len(values) == 0 { - panic("Cannot create row without values unless knowing amount of columns in advance") + panic("Cannot create empty row unless the number of columns is known in advance") } else { tbl.nr_cols = len(values) } } + tbl.nr_rows += 1 + for col in 0.. int { return tbl.nr_rows - 1 } @@ -165,27 +281,24 @@ first_row :: proc(tbl: ^Table) -> int { return header_row(tbl)+1 if tbl.has_header_row else 0 } -build :: proc(tbl: ^Table) { - tbl.dirty = false - +build :: proc(tbl: ^Table, width_proc: Width_Proc) { resize(&tbl.colw, tbl.nr_cols) mem.zero_slice(tbl.colw[:]) for row in 0.. tbl.colw[col] { - tbl.colw[col] = w - } + cell.width = width_proc(cell.text) + tbl.colw[col] = max(tbl.colw[col], cell.width) } } colw_sum := 0 for v in tbl.colw { - colw_sum += v + colw_sum += v + tbl.lpad + tbl.rpad } - tbl.tblw = max(colw_sum, len(tbl.caption) + tbl.lpad + tbl.rpad) + tbl.tblw = max(colw_sum, width_proc(tbl.caption) + tbl.lpad + tbl.rpad) // Resize columns to match total width of table remain := tbl.tblw-colw_sum @@ -198,13 +311,9 @@ build :: proc(tbl: ^Table) { } write_html_table :: proc(w: io.Writer, tbl: ^Table) { - if tbl.dirty { - build(tbl) - } - io.write_string(w, "\n") if tbl.caption != "" { - io.write_string(w, "\n") } @@ -219,45 +328,43 @@ write_html_table :: proc(w: io.Writer, tbl: ^Table) { } if tbl.has_header_row { - io.write_string(w, "\n") - io.write_string(w, " \n") + io.write_string(w, "\t\n") + io.write_string(w, "\t\t\n") for col in 0..") io.write_string(w, cell.text) io.write_string(w, "\n") } - io.write_string(w, " \n") - io.write_string(w, "\n") + io.write_string(w, "\t\t\n") + io.write_string(w, "\t\n") } - io.write_string(w, "\n") + io.write_string(w, "\t\n") for row in 0..\n") + io.write_string(w, "\t\t\n") for col in 0..") io.write_string(w, cell.text) io.write_string(w, "\n") } - io.write_string(w, " \n") + io.write_string(w, "\t\t\n") } - io.write_string(w, " \n") + io.write_string(w, "\t\n") io.write_string(w, "
") + io.write_string(w, "\t") io.write_string(w, tbl.caption) io.write_string(w, "
\n") } -write_ascii_table :: proc(w: io.Writer, tbl: ^Table) { - if tbl.dirty { - build(tbl) - } +write_plain_table :: proc(w: io.Writer, tbl: ^Table, width_proc: Width_Proc = unicode_width_proc) { + build(tbl, width_proc) write_caption_separator :: proc(w: io.Writer, tbl: ^Table) { io.write_byte(w, '+') @@ -271,7 +378,7 @@ write_ascii_table :: proc(w: io.Writer, tbl: ^Table) { if col == 0 { io.write_byte(w, '+') } - write_byte_repeat(w, tbl.colw[col], '-') + write_byte_repeat(w, tbl.colw[col] + tbl.lpad + tbl.rpad, '-') io.write_byte(w, '+') } io.write_byte(w, '\n') @@ -280,8 +387,8 @@ write_ascii_table :: proc(w: io.Writer, tbl: ^Table) { if tbl.caption != "" { write_caption_separator(w, tbl) io.write_byte(w, '|') - write_text_align(w, tbl.tblw - tbl.lpad - tbl.rpad + tbl.nr_cols - 1, - tbl.lpad, tbl.rpad, tbl.caption, .Center) + write_text_align(w, tbl.caption, .Center, + tbl.lpad, tbl.rpad, tbl.tblw + tbl.nr_cols - 1 - width_proc(tbl.caption) - tbl.lpad - tbl.rpad) io.write_byte(w, '|') io.write_byte(w, '\n') } @@ -303,48 +410,123 @@ write_ascii_table :: proc(w: io.Writer, tbl: ^Table) { write_table_separator(w, tbl) } -// Renders table according to GitHub Flavored Markdown (GFM) specification -write_markdown_table :: proc(w: io.Writer, tbl: ^Table) { - // NOTE(oskar): Captions or colspans are not supported by GFM as far as I can tell. - - if tbl.dirty { - build(tbl) +write_decorated_table :: proc(w: io.Writer, tbl: ^Table, decorations: Decorations, width_proc: Width_Proc = unicode_width_proc) { + draw_dividing_row :: proc(w: io.Writer, tbl: ^Table, left, horz, divider, right: string) { + io.write_string(w, left) + for col in 0.. ^Thread { - unimplemented("core:thread procedure not supported on js target") -} - -_start :: proc(t: ^Thread) { - unimplemented("core:thread procedure not supported on js target") -} - -_is_done :: proc(t: ^Thread) -> bool { - unimplemented("core:thread procedure not supported on js target") -} - -_join :: proc(t: ^Thread) { - unimplemented("core:thread procedure not supported on js target") -} - -_join_multiple :: proc(threads: ..^Thread) { - unimplemented("core:thread procedure not supported on js target") -} - -_destroy :: proc(thread: ^Thread) { - unimplemented("core:thread procedure not supported on js target") -} - -_terminate :: proc(using thread : ^Thread, exit_code: int) { - unimplemented("core:thread procedure not supported on js target") -} - -_yield :: proc() { - unimplemented("core:thread procedure not supported on js target") -} - diff --git a/core/thread/thread_other.odin b/core/thread/thread_other.odin new file mode 100644 index 000000000..34bbfda08 --- /dev/null +++ b/core/thread/thread_other.odin @@ -0,0 +1,47 @@ +//+build js, wasi, orca +package thread + +import "base:intrinsics" + +_IS_SUPPORTED :: false + +Thread_Os_Specific :: struct {} + +_thread_priority_map := [Thread_Priority]i32{ + .Normal = 0, + .Low = -2, + .High = +2, +} + +_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { + unimplemented("core:thread procedure not supported on target") +} + +_start :: proc(t: ^Thread) { + unimplemented("core:thread procedure not supported on target") +} + +_is_done :: proc(t: ^Thread) -> bool { + unimplemented("core:thread procedure not supported on target") +} + +_join :: proc(t: ^Thread) { + unimplemented("core:thread procedure not supported on target") +} + +_join_multiple :: proc(threads: ..^Thread) { + unimplemented("core:thread procedure not supported on target") +} + +_destroy :: proc(thread: ^Thread) { + unimplemented("core:thread procedure not supported on target") +} + +_terminate :: proc(using thread : ^Thread, exit_code: int) { + unimplemented("core:thread procedure not supported on target") +} + +_yield :: proc() { + unimplemented("core:thread procedure not supported on target") +} + diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index fddcac89e..da5e116ff 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -44,6 +44,29 @@ Pool :: struct { tasks_done: [dynamic]Task, } +Pool_Thread_Data :: struct { + pool: ^Pool, + task: Task, +} + +@(private="file") +pool_thread_runner :: proc(t: ^Thread) { + data := cast(^Pool_Thread_Data)t.data + pool := data.pool + + for intrinsics.atomic_load(&pool.is_running) { + sync.wait(&pool.sem_available) + + if task, ok := pool_pop_waiting(pool); ok { + data.task = task + pool_do_work(pool, task) + data.task = {} + } + } + + sync.post(&pool.sem_available, 1) +} + // Once initialized, the pool's memory address is not allowed to change until // it is destroyed. // @@ -58,21 +81,11 @@ pool_init :: proc(pool: ^Pool, allocator: mem.Allocator, thread_count: int) { pool.is_running = true for _, i in pool.threads { - t := create(proc(t: ^Thread) { - pool := (^Pool)(t.data) - - for intrinsics.atomic_load(&pool.is_running) { - sync.wait(&pool.sem_available) - - if task, ok := pool_pop_waiting(pool); ok { - pool_do_work(pool, task) - } - } - - sync.post(&pool.sem_available, 1) - }) + t := create(pool_thread_runner) + data := new(Pool_Thread_Data) + data.pool = pool t.user_index = i - t.data = pool + t.data = data pool.threads[i] = t } } @@ -82,6 +95,8 @@ pool_destroy :: proc(pool: ^Pool) { delete(pool.tasks_done) for &t in pool.threads { + data := cast(^Pool_Thread_Data)t.data + free(data, pool.allocator) destroy(t) } @@ -103,7 +118,7 @@ pool_join :: proc(pool: ^Pool) { yield() -started_count: int + started_count: int for started_count < len(pool.threads) { started_count = 0 for t in pool.threads { @@ -138,6 +153,94 @@ pool_add_task :: proc(pool: ^Pool, allocator: mem.Allocator, procedure: Task_Pro sync.post(&pool.sem_available, 1) } +// Forcibly stop a running task by its user index. +// +// This will terminate the underlying thread. Ideally, you should use some +// means of communication to stop a task, as thread termination may leave +// resources unclaimed. +// +// The thread will be restarted to accept new tasks. +// +// Returns true if the task was found and terminated. +pool_stop_task :: proc(pool: ^Pool, user_index: int, exit_code: int = 1) -> bool { + sync.guard(&pool.mutex) + + for t, i in pool.threads { + data := cast(^Pool_Thread_Data)t.data + if data.task.user_index == user_index && data.task.procedure != nil { + terminate(t, exit_code) + + append(&pool.tasks_done, data.task) + intrinsics.atomic_add(&pool.num_done, 1) + intrinsics.atomic_sub(&pool.num_outstanding, 1) + intrinsics.atomic_sub(&pool.num_in_processing, 1) + + destroy(t) + + replacement := create(pool_thread_runner) + replacement.user_index = t.user_index + replacement.data = data + data.task = {} + pool.threads[i] = replacement + + start(replacement) + return true + } + } + + return false +} + +// Forcibly stop all running tasks. +// +// The same notes from `pool_stop_task` apply here. +pool_stop_all_tasks :: proc(pool: ^Pool, exit_code: int = 1) { + sync.guard(&pool.mutex) + + for t, i in pool.threads { + data := cast(^Pool_Thread_Data)t.data + if data.task.procedure != nil { + terminate(t, exit_code) + + append(&pool.tasks_done, data.task) + intrinsics.atomic_add(&pool.num_done, 1) + intrinsics.atomic_sub(&pool.num_outstanding, 1) + intrinsics.atomic_sub(&pool.num_in_processing, 1) + + destroy(t) + + replacement := create(pool_thread_runner) + replacement.user_index = t.user_index + replacement.data = data + data.task = {} + pool.threads[i] = replacement + + start(replacement) + } + } +} + +// Force the pool to stop all of its threads and put it into a state where +// it will no longer run any more tasks. +// +// The pool must still be destroyed after this. +pool_shutdown :: proc(pool: ^Pool, exit_code: int = 1) { + intrinsics.atomic_store(&pool.is_running, false) + sync.guard(&pool.mutex) + + for t in pool.threads { + terminate(t, exit_code) + + data := cast(^Pool_Thread_Data)t.data + if data.task.procedure != nil { + append(&pool.tasks_done, data.task) + intrinsics.atomic_add(&pool.num_done, 1) + intrinsics.atomic_sub(&pool.num_outstanding, 1) + intrinsics.atomic_sub(&pool.num_in_processing, 1) + } + } +} + // Number of tasks waiting to be processed. Only informational, mostly for // debugging. Don't rely on this value being consistent with other num_* // values. diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index c75710873..f56454bfc 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -1,12 +1,14 @@ -// +build linux, darwin, freebsd, openbsd, haiku +// +build linux, darwin, freebsd, openbsd, netbsd, haiku // +private package thread -import "base:intrinsics" import "core:sync" import "core:sys/unix" +import "core:time" -CAS :: intrinsics.atomic_compare_exchange_strong +_IS_SUPPORTED :: true + +CAS :: sync.atomic_compare_exchange_strong // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. // Also see core/sys/darwin/mach_darwin.odin/semaphore_t. @@ -20,28 +22,30 @@ Thread_Os_Specific :: struct #align(16) { // It then waits for `start` to be called. // _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { - __linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr { + __unix_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr { t := (^Thread)(t) - when ODIN_OS != .Darwin { - // We need to give the thread a moment to start up before we enable cancellation. - can_set_thread_cancel_state := unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_DISABLE, nil) == 0 - } + // We need to give the thread a moment to start up before we enable cancellation. + can_set_thread_cancel_state := unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_ENABLE, nil) == 0 sync.lock(&t.mutex) t.id = sync.current_thread_id() - for (.Started not_in t.flags) { - sync.wait(&t.cond, &t.mutex) + for (.Started not_in sync.atomic_load(&t.flags)) { + // HACK: use a timeout so in the event that the condition is signalled at THIS comment's exact point + // (after checking flags, before starting the wait) it gets itself out of that deadlock after a ms. + sync.wait_with_timeout(&t.cond, &t.mutex, time.Millisecond) } - when ODIN_OS != .Darwin { - // Enable thread's cancelability. - if can_set_thread_cancel_state { - unix.pthread_setcanceltype (unix.PTHREAD_CANCEL_ASYNCHRONOUS, nil) - unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_DISABLE, nil) - } + if .Joined in sync.atomic_load(&t.flags) { + return nil + } + + // Enable thread's cancelability. + if can_set_thread_cancel_state { + unix.pthread_setcanceltype (unix.PTHREAD_CANCEL_ASYNCHRONOUS, nil) + unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_ENABLE, nil) } { @@ -56,11 +60,11 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { t.procedure(t) } - intrinsics.atomic_store(&t.flags, t.flags + { .Done }) + sync.atomic_or(&t.flags, { .Done }) sync.unlock(&t.mutex) - if .Self_Cleanup in t.flags { + if .Self_Cleanup in sync.atomic_load(&t.flags) { t.unix_thread = {} // NOTE(ftphikari): It doesn't matter which context 'free' received, right? context = {} @@ -77,9 +81,12 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { defer unix.pthread_attr_destroy(&attrs) // NOTE(tetra, 2019-11-01): These only fail if their argument is invalid. - assert(unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) == 0) - when ODIN_OS != .Haiku { - assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0) + res: i32 + res = unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) + assert(res == 0) + when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { + res = unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) + assert(res == 0) } thread := new(Thread) @@ -90,8 +97,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // Set thread priority. policy: i32 - res: i32 - when ODIN_OS != .Haiku { + when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { res = unix.pthread_attr_getschedpolicy(&attrs, &policy) assert(res == 0) } @@ -109,7 +115,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { assert(res == 0) thread.procedure = procedure - if unix.pthread_create(&thread.unix_thread, &attrs, __linux_thread_entry_proc, thread) != 0 { + if unix.pthread_create(&thread.unix_thread, &attrs, __unix_thread_entry_proc, thread) != 0 { free(thread, thread.creation_allocator) return nil } @@ -118,13 +124,12 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { } _start :: proc(t: ^Thread) { - // sync.guard(&t.mutex) - t.flags += { .Started } + sync.atomic_or(&t.flags, { .Started }) sync.signal(&t.cond) } _is_done :: proc(t: ^Thread) -> bool { - return .Done in intrinsics.atomic_load(&t.flags) + return .Done in sync.atomic_load(&t.flags) } _join :: proc(t: ^Thread) { @@ -135,7 +140,7 @@ _join :: proc(t: ^Thread) { } // Preserve other flags besides `.Joined`, like `.Started`. - unjoined := intrinsics.atomic_load(&t.flags) - {.Joined} + unjoined := sync.atomic_load(&t.flags) - {.Joined} joined := unjoined + {.Joined} // Try to set `t.flags` from unjoined to joined. If it returns joined, @@ -143,6 +148,11 @@ _join :: proc(t: ^Thread) { if res, ok := CAS(&t.flags, unjoined, joined); res == joined && !ok { return } + // Prevent non-started threads from blocking main thread with initial wait + // condition. + if .Started not_in unjoined { + _start(t) + } unix.pthread_join(t.unix_thread, nil) } @@ -159,10 +169,17 @@ _destroy :: proc(t: ^Thread) { } _terminate :: proc(t: ^Thread, exit_code: int) { - // `pthread_cancel` is unreliable on Darwin for unknown reasons. - when ODIN_OS != .Darwin { - unix.pthread_cancel(t.unix_thread) - } + // NOTE(Feoramund): For thread cancellation to succeed on BSDs and + // possibly Darwin systems, the thread must call one of the pthread + // cancelation points at some point after this. + // + // The most obvious one of these is `pthread_cancel`, but there is an + // entire list of functions that act as cancelation points available in the + // pthreads manual page. + // + // This is in contrast to behavior I have seen on Linux where the thread is + // just terminated. + unix.pthread_cancel(t.unix_thread) } _yield :: proc() { diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index e85b2b62a..8da75a2d9 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -6,6 +6,8 @@ import "base:intrinsics" import "core:sync" import win32 "core:sys/windows" +_IS_SUPPORTED :: true + Thread_Os_Specific :: struct { win32_thread: win32.HANDLE, win32_thread_id: win32.DWORD, @@ -24,6 +26,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { __windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD { t := (^Thread)(t_) + if .Joined in t.flags { + return 0 + } + t.id = sync.current_thread_id() { @@ -93,11 +99,16 @@ _join :: proc(t: ^Thread) { return } + t.flags += {.Joined} + + if .Started not_in t.flags { + t.flags += {.Started} + win32.ResumeThread(t.win32_thread) + } + win32.WaitForSingleObject(t.win32_thread, win32.INFINITE) win32.CloseHandle(t.win32_thread) t.win32_thread = win32.INVALID_HANDLE - - t.flags += {.Joined} } _join_multiple :: proc(threads: ..^Thread) { diff --git a/core/time/datetime/datetime.odin b/core/time/datetime/datetime.odin index e15ced5a5..938b4a368 100644 --- a/core/time/datetime/datetime.odin +++ b/core/time/datetime/datetime.odin @@ -48,7 +48,7 @@ ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateT } day_of_week :: proc "contextless" (ordinal: Ordinal) -> (day: Weekday) { - return Weekday((ordinal - EPOCH) %% 7) + return Weekday((ordinal - EPOCH + 1) %% 7) } subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) { @@ -127,13 +127,13 @@ days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: return delta.days, .None } -last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i64, err: Error) { +last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i8, err: Error) { // Not using formula 2.27 from the book. This is far simpler and gives the same answer. validate(Date{year, month, 1}) or_return month_days := MONTH_DAYS - day = i64(month_days[month]) + day = month_days[month] if month == 2 && is_leap_year(year) { day += 1 } diff --git a/core/time/iso8601.odin b/core/time/iso8601.odin new file mode 100644 index 000000000..528e0b00a --- /dev/null +++ b/core/time/iso8601.odin @@ -0,0 +1,113 @@ +package time +// Parsing ISO 8601 date/time strings into time.Time. + +import dt "core:time/datetime" + +// Parses an ISO 8601 string and returns Time in UTC, with any UTC offset applied to it. +// Only 4-digit years are accepted. +// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. +// Leap seconds are smeared into 23:59:59. +iso8601_to_time_utc :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) { + offset: int + + res, offset, consumed = iso8601_to_time_and_offset(iso_datetime, is_leap) + res._nsec += (i64(-offset) * i64(Minute)) + return res, consumed +} + +// Parses an ISO 8601 string and returns Time and a UTC offset in minutes. +// e.g. 1985-04-12T23:20:50.52Z +// Note: Only 4-digit years are accepted. +// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. +// Leap seconds are smeared into 23:59:59. +iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) { + moment, offset, leap_second, count := iso8601_to_components(iso_datetime) + if count == 0 { + return + } + + if is_leap != nil { + is_leap^ = leap_second + } + + if _res, ok := datetime_to_time(moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, moment.nano); !ok { + return {}, 0, 0 + } else { + return _res, offset, count + } +} + +// Parses an ISO 8601 string and returns Time and a UTC offset in minutes. +// e.g. 1985-04-12T23:20:50.52Z +// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given +iso8601_to_components :: proc(iso_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) { + moment, offset, count, leap_second, ok := _iso8601_to_components(iso_datetime) + if !ok { + return + } + return moment, offset, leap_second, count +} + +// Parses an ISO 8601 string and returns datetime.DateTime. +// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given +@(private) +_iso8601_to_components :: proc(iso_datetime: string) -> (res: dt.DateTime, utc_offset: int, consumed: int, is_leap: bool, ok: bool) { + // A compliant date is at minimum 20 characters long, e.g. YYYY-MM-DDThh:mm:ssZ + (len(iso_datetime) >= 20) or_return + + // Scan and eat YYYY-MM-DD[Tt], then scan and eat HH:MM:SS, leave separator + year := scan_digits(iso_datetime[0:], "-", 4) or_return + month := scan_digits(iso_datetime[5:], "-", 2) or_return + day := scan_digits(iso_datetime[8:], "Tt ", 2) or_return + hour := scan_digits(iso_datetime[11:], ":", 2) or_return + minute := scan_digits(iso_datetime[14:], ":", 2) or_return + second := scan_digits(iso_datetime[17:], "", 2) or_return + nanos := 0 + count := 19 + + // Scan fractional seconds + if iso_datetime[count] == '.' { + count += 1 // consume '.' + multiplier := 100_000_000 + for digit in iso_datetime[count:] { + if multiplier >= 1 && int(digit) >= '0' && int(digit) <= '9' { + nanos += int(digit - '0') * multiplier + multiplier /= 10 + count += 1 + } else { + break + } + } + } + + // Leap second handling + if minute == 59 && second == 60 { + second = 59 + is_leap = true + } + + err: dt.Error + if res, err = dt.components_to_datetime(year, month, day, hour, minute, second, nanos); err != .None { + return {}, 0, 0, false, false + } + + if len(iso_datetime[count:]) == 0 { + return res, utc_offset, count, is_leap, true + } + + // Scan UTC offset + switch iso_datetime[count] { + case 'Z', 'z': + utc_offset = 0 + count += 1 + case '+', '-': + (len(iso_datetime[count:]) >= 6) or_return + offset_hour := scan_digits(iso_datetime[count+1:], ":", 2) or_return + offset_minute := scan_digits(iso_datetime[count+4:], "", 2) or_return + + utc_offset = 60 * offset_hour + offset_minute + utc_offset *= -1 if iso_datetime[count] == '-' else 1 + count += 6 + } + return res, utc_offset, count, is_leap, true +} \ No newline at end of file diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin index 30c255c79..0a2d431b7 100644 --- a/core/time/rfc3339.odin +++ b/core/time/rfc3339.odin @@ -57,12 +57,12 @@ _rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_o (len(rfc_datetime) >= 20) or_return // Scan and eat YYYY-MM-DD[Tt], then scan and eat HH:MM:SS, leave separator - year := scan_digits(rfc_datetime[0:], "-", 4) or_return - month := scan_digits(rfc_datetime[5:], "-", 2) or_return - day := scan_digits(rfc_datetime[8:], "Tt", 2) or_return - hour := scan_digits(rfc_datetime[11:], ":", 2) or_return - minute := scan_digits(rfc_datetime[14:], ":", 2) or_return - second := scan_digits(rfc_datetime[17:], "", 2) or_return + year := scan_digits(rfc_datetime[0:], "-", 4) or_return + month := scan_digits(rfc_datetime[5:], "-", 2) or_return + day := scan_digits(rfc_datetime[8:], "Tt ", 2) or_return + hour := scan_digits(rfc_datetime[11:], ":", 2) or_return + minute := scan_digits(rfc_datetime[14:], ":", 2) or_return + second := scan_digits(rfc_datetime[17:], "", 2) or_return nanos := 0 count := 19 @@ -87,7 +87,7 @@ _rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_o // Scan UTC offset switch rfc_datetime[count] { - case 'Z': + case 'Z', 'z': utc_offset = 0 count += 1 case '+', '-': diff --git a/core/time/time.odin b/core/time/time.odin index 4807af840..4ea5afc70 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -220,6 +220,10 @@ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time { return Time{(sec*1e9 + nsec)} } +from_nanoseconds :: #force_inline proc "contextless" (nsec: i64) -> Time { + return Time{nsec} +} + to_unix_seconds :: time_to_unix time_to_unix :: proc "contextless" (t: Time) -> i64 { return t._nsec/1e9 @@ -389,6 +393,7 @@ is_leap_year :: proc "contextless" (year: int) -> (leap: bool) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) } +@(rodata) days_before := [?]i32{ 0, 31, diff --git a/core/time/time_orca.odin b/core/time/time_orca.odin new file mode 100644 index 000000000..d222c8247 --- /dev/null +++ b/core/time/time_orca.odin @@ -0,0 +1,24 @@ +//+private +//+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 1c46b5994..f3e0d6640 100644 --- a/core/time/time_unix.odin +++ b/core/time/time_unix.odin @@ -1,5 +1,5 @@ //+private -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku package time import "core:sys/unix" diff --git a/core/time/time_wasi.odin b/core/time/time_wasi.odin index dacf911fc..3a5554d67 100644 --- a/core/time/time_wasi.odin +++ b/core/time/time_wasi.odin @@ -2,8 +2,6 @@ //+build wasi package time -import wasi "core:sys/wasm/wasi" - _IS_SUPPORTED :: false _now :: proc "contextless" () -> Time { diff --git a/core/time/tsc_darwin.odin b/core/time/tsc_darwin.odin index 6688ae7d8..841c0b692 100644 --- a/core/time/tsc_darwin.odin +++ b/core/time/tsc_darwin.odin @@ -1,21 +1,10 @@ //+private -//+build darwin package time -import "core:c" +import "core:sys/unix" -foreign import libc "system:System.framework" -foreign libc { - @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- -} - -_get_tsc_frequency :: proc "contextless" () -> (u64, bool) { - tmp_freq : u64 = 0 - tmp_size : i64 = size_of(tmp_freq) - ret := _sysctlbyname("machdep.tsc.frequency", &tmp_freq, &tmp_size, nil, 0) - if ret < 0 { - return 0, false - } - - return tmp_freq, true +_get_tsc_frequency :: proc "contextless" () -> (freq: u64, ok: bool) { + unix.sysctlbyname("machdep.tsc.frequency", &freq) or_return + ok = true + return } diff --git a/core/unicode/letter.odin b/core/unicode/letter.odin index 891c90bf3..e6d0261c6 100644 --- a/core/unicode/letter.odin +++ b/core/unicode/letter.odin @@ -5,6 +5,12 @@ REPLACEMENT_CHAR :: '\ufffd' // Represented an invalid code point MAX_ASCII :: '\u007f' // Maximum ASCII value MAX_LATIN1 :: '\u00ff' // Maximum Latin-1 value +ZERO_WIDTH_SPACE :: '\u200B' +ZERO_WIDTH_NON_JOINER :: '\u200C' +ZERO_WIDTH_JOINER :: '\u200D' +WORD_JOINER :: '\u2060' + +@(require_results) binary_search :: proc(c: i32, table: []i32, length, stride: int) -> int { n := length t := 0 @@ -24,6 +30,7 @@ binary_search :: proc(c: i32, table: []i32, length, stride: int) -> int { return -1 } +@(require_results) to_lower :: proc(r: rune) -> rune { c := i32(r) p := binary_search(c, to_lower_ranges[:], len(to_lower_ranges)/3, 3) @@ -36,6 +43,7 @@ to_lower :: proc(r: rune) -> rune { } return rune(c) } +@(require_results) to_upper :: proc(r: rune) -> rune { c := i32(r) p := binary_search(c, to_upper_ranges[:], len(to_upper_ranges)/3, 3) @@ -48,6 +56,7 @@ to_upper :: proc(r: rune) -> rune { } return rune(c) } +@(require_results) to_title :: proc(r: rune) -> rune { c := i32(r) p := binary_search(c, to_upper_singlets[:], len(to_title_singlets)/2, 2) @@ -58,6 +67,7 @@ to_title :: proc(r: rune) -> rune { } +@(require_results) is_lower :: proc(r: rune) -> bool { if r <= MAX_ASCII { return u32(r)-'a' < 26 @@ -74,6 +84,7 @@ is_lower :: proc(r: rune) -> bool { return false } +@(require_results) is_upper :: proc(r: rune) -> bool { if r <= MAX_ASCII { return u32(r)-'A' < 26 @@ -91,6 +102,7 @@ is_upper :: proc(r: rune) -> bool { } is_alpha :: is_letter +@(require_results) is_letter :: proc(r: rune) -> bool { if u32(r) <= MAX_LATIN1 { return char_properties[u8(r)]&pLmask != 0 @@ -111,10 +123,12 @@ is_letter :: proc(r: rune) -> bool { return false } +@(require_results) is_title :: proc(r: rune) -> bool { return is_upper(r) && is_lower(r) } +@(require_results) is_digit :: proc(r: rune) -> bool { if r <= MAX_LATIN1 { return '0' <= r && r <= '9' @@ -124,6 +138,7 @@ is_digit :: proc(r: rune) -> bool { is_white_space :: is_space +@(require_results) is_space :: proc(r: rune) -> bool { if u32(r) <= MAX_LATIN1 { switch r { @@ -140,18 +155,20 @@ is_space :: proc(r: rune) -> bool { return false } +@(require_results) is_combining :: proc(r: rune) -> bool { c := i32(r) return c >= 0x0300 && (c <= 0x036f || - (c >= 0x1ab0 && c <= 0x1aff) || - (c >= 0x1dc0 && c <= 0x1dff) || - (c >= 0x20d0 && c <= 0x20ff) || - (c >= 0xfe20 && c <= 0xfe2f)) + (c >= 0x1ab0 && c <= 0x1aff) || + (c >= 0x1dc0 && c <= 0x1dff) || + (c >= 0x20d0 && c <= 0x20ff) || + (c >= 0xfe20 && c <= 0xfe2f)) } +@(require_results) is_graphic :: proc(r: rune) -> bool { if u32(r) <= MAX_LATIN1 { return char_properties[u8(r)]&pg != 0 @@ -159,6 +176,7 @@ is_graphic :: proc(r: rune) -> bool { return false } +@(require_results) is_print :: proc(r: rune) -> bool { if u32(r) <= MAX_LATIN1 { return char_properties[u8(r)]&pp != 0 @@ -166,6 +184,7 @@ is_print :: proc(r: rune) -> bool { return false } +@(require_results) is_control :: proc(r: rune) -> bool { if u32(r) <= MAX_LATIN1 { return char_properties[u8(r)]&pC != 0 @@ -173,6 +192,7 @@ is_control :: proc(r: rune) -> bool { return false } +@(require_results) is_number :: proc(r: rune) -> bool { if u32(r) <= MAX_LATIN1 { return char_properties[u8(r)]&pN != 0 @@ -180,6 +200,7 @@ is_number :: proc(r: rune) -> bool { return false } +@(require_results) is_punct :: proc(r: rune) -> bool { if u32(r) <= MAX_LATIN1 { return char_properties[u8(r)]&pP != 0 @@ -187,9 +208,285 @@ is_punct :: proc(r: rune) -> bool { return false } +@(require_results) is_symbol :: proc(r: rune) -> bool { if u32(r) <= MAX_LATIN1 { return char_properties[u8(r)]&pS != 0 } return false } + +// +// The procedures below are accurate as of Unicode 15.1.0. +// + +// Emoji_Modifier +@(require_results) +is_emoji_modifier :: proc(r: rune) -> bool { + return 0x1F3FB <= r && r <= 0x1F3FF +} + +// Regional_Indicator +@(require_results) +is_regional_indicator :: proc(r: rune) -> bool { + return 0x1F1E6 <= r && r <= 0x1F1FF +} + +// General_Category=Enclosing_Mark +@(require_results) +is_enclosing_mark :: proc(r: rune) -> bool { + switch r { + case 0x0488, + 0x0489, + 0x1ABE, + 0x20DD ..= 0x20E0, + 0x20E2 ..= 0x20E4, + 0xA670 ..= 0xA672: + return true + } + + return false +} + +// Prepended_Concatenation_Mark +@(require_results) +is_prepended_concatenation_mark :: proc(r: rune) -> bool { + switch r { + case 0x00600 ..= 0x00605, + 0x006DD, + 0x0070F, + 0x00890 ..= 0x00891, + 0x008E2, + 0x110BD, + 0x110CD: + return true + case: + return false + } +} + +// General_Category=Spacing_Mark +@(require_results) +is_spacing_mark :: proc(r: rune) -> bool { + c := i32(r) + p := binary_search(c, spacing_mark_ranges[:], len(spacing_mark_ranges)/2, 2) + if p >= 0 && spacing_mark_ranges[p] <= c && c <= spacing_mark_ranges[p+1] { + return true + } + return false +} + +// General_Category=Nonspacing_Mark +@(require_results) +is_nonspacing_mark :: proc(r: rune) -> bool { + c := i32(r) + p := binary_search(c, nonspacing_mark_ranges[:], len(nonspacing_mark_ranges)/2, 2) + if p >= 0 && nonspacing_mark_ranges[p] <= c && c <= nonspacing_mark_ranges[p+1] { + return true + } + return false +} + +// Extended_Pictographic +@(require_results) +is_emoji_extended_pictographic :: proc(r: rune) -> bool { + c := i32(r) + p := binary_search(c, emoji_extended_pictographic_ranges[:], len(emoji_extended_pictographic_ranges)/2, 2) + if p >= 0 && emoji_extended_pictographic_ranges[p] <= c && c <= emoji_extended_pictographic_ranges[p+1] { + return true + } + return false +} + +// Grapheme_Extend +@(require_results) +is_grapheme_extend :: proc(r: rune) -> bool { + c := i32(r) + p := binary_search(c, grapheme_extend_ranges[:], len(grapheme_extend_ranges)/2, 2) + if p >= 0 && grapheme_extend_ranges[p] <= c && c <= grapheme_extend_ranges[p+1] { + return true + } + return false +} + + +// Hangul_Syllable_Type=Leading_Jamo +@(require_results) +is_hangul_syllable_leading :: proc(r: rune) -> bool { + return 0x1100 <= r && r <= 0x115F || 0xA960 <= r && r <= 0xA97C +} + +// Hangul_Syllable_Type=Vowel_Jamo +@(require_results) +is_hangul_syllable_vowel :: proc(r: rune) -> bool { + return 0x1160 <= r && r <= 0x11A7 || 0xD7B0 <= r && r <= 0xD7C6 +} + +// Hangul_Syllable_Type=Trailing_Jamo +@(require_results) +is_hangul_syllable_trailing :: proc(r: rune) -> bool { + return 0x11A8 <= r && r <= 0x11FF || 0xD7CB <= r && r <= 0xD7FB +} + +// Hangul_Syllable_Type=LV_Syllable +@(require_results) +is_hangul_syllable_lv :: proc(r: rune) -> bool { + c := i32(r) + p := binary_search(c, hangul_syllable_lv_singlets[:], len(hangul_syllable_lv_singlets), 1) + if p >= 0 && c == hangul_syllable_lv_singlets[p] { + return true + } + return false +} + +// Hangul_Syllable_Type=LVT_Syllable +@(require_results) +is_hangul_syllable_lvt :: proc(r: rune) -> bool { + c := i32(r) + p := binary_search(c, hangul_syllable_lvt_ranges[:], len(hangul_syllable_lvt_ranges)/2, 2) + if p >= 0 && hangul_syllable_lvt_ranges[p] <= c && c <= hangul_syllable_lvt_ranges[p+1] { + return true + } + return false +} + + +// Indic_Syllabic_Category=Consonant_Preceding_Repha +@(require_results) +is_indic_consonant_preceding_repha :: proc(r: rune) -> bool { + switch r { + case 0x00D4E, + 0x11941, + 0x11D46, + 0x11F02: + return true + case: + return false + } +} + +// Indic_Syllabic_Category=Consonant_Prefixed +@(require_results) +is_indic_consonant_prefixed :: proc(r: rune) -> bool { + switch r { + case 0x111C2 ..= 0x111C3, + 0x1193F, + 0x11A3A, + 0x11A84 ..= 0x11A89: + return true + case: + return false + } +} + +// Indic_Conjunct_Break=Linker +@(require_results) +is_indic_conjunct_break_linker :: proc(r: rune) -> bool { + switch r { + case 0x094D, + 0x09CD, + 0x0ACD, + 0x0B4D, + 0x0C4D, + 0x0D4D: + return true + case: + return false + } +} + +// Indic_Conjunct_Break=Consonant +@(require_results) +is_indic_conjunct_break_consonant :: proc(r: rune) -> bool { + c := i32(r) + p := binary_search(c, indic_conjunct_break_consonant_ranges[:], len(indic_conjunct_break_consonant_ranges)/2, 2) + if p >= 0 && indic_conjunct_break_consonant_ranges[p] <= c && c <= indic_conjunct_break_consonant_ranges[p+1] { + return true + } + return false +} + +// Indic_Conjunct_Break=Extend +@(require_results) +is_indic_conjunct_break_extend :: proc(r: rune) -> bool { + c := i32(r) + p := binary_search(c, indic_conjunct_break_extend_ranges[:], len(indic_conjunct_break_extend_ranges)/2, 2) + if p >= 0 && indic_conjunct_break_extend_ranges[p] <= c && c <= indic_conjunct_break_extend_ranges[p+1] { + return true + } + return false +} + + +/* +For grapheme text segmentation, from Unicode TR 29 Rev 43: + +``` +Indic_Syllabic_Category = Consonant_Preceding_Repha, or +Indic_Syllabic_Category = Consonant_Prefixed, or +Prepended_Concatenation_Mark = Yes +``` +*/ +@(require_results) +is_gcb_prepend_class :: proc(r: rune) -> bool { + return is_indic_consonant_preceding_repha(r) || is_indic_consonant_prefixed(r) || is_prepended_concatenation_mark(r) +} + +/* +For grapheme text segmentation, from Unicode TR 29 Rev 43: + +``` +Grapheme_Extend = Yes, or +Emoji_Modifier = Yes + +This includes: +General_Category = Nonspacing_Mark +General_Category = Enclosing_Mark +U+200C ZERO WIDTH NON-JOINER + +plus a few General_Category = Spacing_Mark needed for canonical equivalence. +``` +*/ +@(require_results) +is_gcb_extend_class :: proc(r: rune) -> bool { + return is_grapheme_extend(r) || is_emoji_modifier(r) +} + +// Return values: +// +// - 2 if East_Asian_Width=F or W, or +// - 0 if non-printable / zero-width, or +// - 1 in all other cases. +// +@(require_results) +normalized_east_asian_width :: proc(r: rune) -> int { + // This is a different interpretation of the BOM which occurs in the middle of text. + ZERO_WIDTH_NO_BREAK_SPACE :: '\uFEFF' + + if is_control(r) { + return 0 + } else if r <= 0x10FF { + // Easy early out for low runes. + return 1 + } + + switch r { + case ZERO_WIDTH_NO_BREAK_SPACE, + ZERO_WIDTH_SPACE, + ZERO_WIDTH_NON_JOINER, + ZERO_WIDTH_JOINER, + WORD_JOINER: + return 0 + } + + c := i32(r) + p := binary_search(c, normalized_east_asian_width_ranges[:], len(normalized_east_asian_width_ranges)/3, 3) + if p >= 0 && normalized_east_asian_width_ranges[p] <= c && c <= normalized_east_asian_width_ranges[p+1] { + return cast(int)normalized_east_asian_width_ranges[p+2] + } + return 1 +} + +// +// End of Unicode 15.1.0 block. +// diff --git a/core/unicode/tables.odin b/core/unicode/tables.odin index f43827413..c0b3fe434 100644 --- a/core/unicode/tables.odin +++ b/core/unicode/tables.odin @@ -12,6 +12,7 @@ package unicode @(private) pLo :: pLl | pLu // a letter that is neither upper nor lower case. @(private) pLmask :: pLo +@(rodata) char_properties := [MAX_LATIN1+1]u8{ 0x00 = pC, // '\x00' 0x01 = pC, // '\x01' @@ -272,6 +273,7 @@ char_properties := [MAX_LATIN1+1]u8{ } +@(rodata) alpha_ranges := [?]i32{ 0x00d8, 0x00f6, 0x00f8, 0x01f5, @@ -427,6 +429,7 @@ alpha_ranges := [?]i32{ 0xffda, 0xffdc, } +@(rodata) alpha_singlets := [?]i32{ 0x00aa, 0x00b5, @@ -462,6 +465,7 @@ alpha_singlets := [?]i32{ 0xfe74, } +@(rodata) space_ranges := [?]i32{ 0x0009, 0x000d, // tab and newline 0x0020, 0x0020, // space @@ -477,6 +481,7 @@ space_ranges := [?]i32{ 0xfeff, 0xfeff, } +@(rodata) unicode_spaces := [?]i32{ 0x0009, // tab 0x000a, // LF @@ -494,6 +499,7 @@ unicode_spaces := [?]i32{ 0xfeff, // unknown } +@(rodata) to_upper_ranges := [?]i32{ 0x0061, 0x007a, 468, // a-z A-Z 0x00e0, 0x00f6, 468, @@ -532,6 +538,7 @@ to_upper_ranges := [?]i32{ 0xff41, 0xff5a, 468, } +@(rodata) to_upper_singlets := [?]i32{ 0x00ff, 621, 0x0101, 499, @@ -875,6 +882,7 @@ to_upper_singlets := [?]i32{ 0x1ff3, 509, } +@(rodata) to_lower_ranges := [?]i32{ 0x0041, 0x005a, 532, // A-Z a-z 0x00c0, 0x00d6, 532, // - - @@ -914,6 +922,7 @@ to_lower_ranges := [?]i32{ 0xff21, 0xff3a, 532, // - - } +@(rodata) to_lower_singlets := [?]i32{ 0x0100, 501, 0x0102, 501, @@ -1250,6 +1259,7 @@ to_lower_singlets := [?]i32{ 0x1ffc, 491, } +@(rodata) to_title_singlets := [?]i32{ 0x01c4, 501, 0x01c6, 499, @@ -1260,3 +1270,2623 @@ to_title_singlets := [?]i32{ 0x01f1, 501, 0x01f3, 499, } + +// +// The tables below are accurate as of Unicode 15.1.0. +// + +@(rodata) +spacing_mark_ranges := [?]i32 { + 0x0903, 0x0903, + 0x093B, 0x093B, + 0x093E, 0x0940, + 0x0949, 0x094C, + 0x094E, 0x094F, + 0x0982, 0x0983, + 0x09BE, 0x09C0, + 0x09C7, 0x09C8, + 0x09CB, 0x09CC, + 0x09D7, 0x09D7, + 0x0A03, 0x0A03, + 0x0A3E, 0x0A40, + 0x0A83, 0x0A83, + 0x0ABE, 0x0AC0, + 0x0AC9, 0x0AC9, + 0x0ACB, 0x0ACC, + 0x0B02, 0x0B03, + 0x0B3E, 0x0B3E, + 0x0B40, 0x0B40, + 0x0B47, 0x0B48, + 0x0B4B, 0x0B4C, + 0x0B57, 0x0B57, + 0x0BBE, 0x0BBF, + 0x0BC1, 0x0BC2, + 0x0BC6, 0x0BC8, + 0x0BCA, 0x0BCC, + 0x0BD7, 0x0BD7, + 0x0C01, 0x0C03, + 0x0C41, 0x0C44, + 0x0C82, 0x0C83, + 0x0CBE, 0x0CBE, + 0x0CC0, 0x0CC4, + 0x0CC7, 0x0CC8, + 0x0CCA, 0x0CCB, + 0x0CD5, 0x0CD6, + 0x0CF3, 0x0CF3, + 0x0D02, 0x0D03, + 0x0D3E, 0x0D40, + 0x0D46, 0x0D48, + 0x0D4A, 0x0D4C, + 0x0D57, 0x0D57, + 0x0D82, 0x0D83, + 0x0DCF, 0x0DD1, + 0x0DD8, 0x0DDF, + 0x0DF2, 0x0DF3, + 0x0F3E, 0x0F3F, + 0x0F7F, 0x0F7F, + 0x102B, 0x102C, + 0x1031, 0x1031, + 0x1038, 0x1038, + 0x103B, 0x103C, + 0x1056, 0x1057, + 0x1062, 0x1064, + 0x1067, 0x106D, + 0x1083, 0x1084, + 0x1087, 0x108C, + 0x108F, 0x108F, + 0x109A, 0x109C, + 0x1715, 0x1715, + 0x1734, 0x1734, + 0x17B6, 0x17B6, + 0x17BE, 0x17C5, + 0x17C7, 0x17C8, + 0x1923, 0x1926, + 0x1929, 0x192B, + 0x1930, 0x1931, + 0x1933, 0x1938, + 0x1A19, 0x1A1A, + 0x1A55, 0x1A55, + 0x1A57, 0x1A57, + 0x1A61, 0x1A61, + 0x1A63, 0x1A64, + 0x1A6D, 0x1A72, + 0x1B04, 0x1B04, + 0x1B35, 0x1B35, + 0x1B3B, 0x1B3B, + 0x1B3D, 0x1B41, + 0x1B43, 0x1B44, + 0x1B82, 0x1B82, + 0x1BA1, 0x1BA1, + 0x1BA6, 0x1BA7, + 0x1BAA, 0x1BAA, + 0x1BE7, 0x1BE7, + 0x1BEA, 0x1BEC, + 0x1BEE, 0x1BEE, + 0x1BF2, 0x1BF3, + 0x1C24, 0x1C2B, + 0x1C34, 0x1C35, + 0x1CE1, 0x1CE1, + 0x1CF7, 0x1CF7, + 0x302E, 0x302F, + 0xA823, 0xA824, + 0xA827, 0xA827, + 0xA880, 0xA881, + 0xA8B4, 0xA8C3, + 0xA952, 0xA953, + 0xA983, 0xA983, + 0xA9B4, 0xA9B5, + 0xA9BA, 0xA9BB, + 0xA9BE, 0xA9C0, + 0xAA2F, 0xAA30, + 0xAA33, 0xAA34, + 0xAA4D, 0xAA4D, + 0xAA7B, 0xAA7B, + 0xAA7D, 0xAA7D, + 0xAAEB, 0xAAEB, + 0xAAEE, 0xAAEF, + 0xAAF5, 0xAAF5, + 0xABE3, 0xABE4, + 0xABE6, 0xABE7, + 0xABE9, 0xABEA, + 0xABEC, 0xABEC, + 0x11000, 0x11000, + 0x11002, 0x11002, + 0x11082, 0x11082, + 0x110B0, 0x110B2, + 0x110B7, 0x110B8, + 0x1112C, 0x1112C, + 0x11145, 0x11146, + 0x11182, 0x11182, + 0x111B3, 0x111B5, + 0x111BF, 0x111C0, + 0x111CE, 0x111CE, + 0x1122C, 0x1122E, + 0x11232, 0x11233, + 0x11235, 0x11235, + 0x112E0, 0x112E2, + 0x11302, 0x11303, + 0x1133E, 0x1133F, + 0x11341, 0x11344, + 0x11347, 0x11348, + 0x1134B, 0x1134D, + 0x11357, 0x11357, + 0x11362, 0x11363, + 0x11435, 0x11437, + 0x11440, 0x11441, + 0x11445, 0x11445, + 0x114B0, 0x114B2, + 0x114B9, 0x114B9, + 0x114BB, 0x114BE, + 0x114C1, 0x114C1, + 0x115AF, 0x115B1, + 0x115B8, 0x115BB, + 0x115BE, 0x115BE, + 0x11630, 0x11632, + 0x1163B, 0x1163C, + 0x1163E, 0x1163E, + 0x116AC, 0x116AC, + 0x116AE, 0x116AF, + 0x116B6, 0x116B6, + 0x11720, 0x11721, + 0x11726, 0x11726, + 0x1182C, 0x1182E, + 0x11838, 0x11838, + 0x11930, 0x11935, + 0x11937, 0x11938, + 0x1193D, 0x1193D, + 0x11940, 0x11940, + 0x11942, 0x11942, + 0x119D1, 0x119D3, + 0x119DC, 0x119DF, + 0x119E4, 0x119E4, + 0x11A39, 0x11A39, + 0x11A57, 0x11A58, + 0x11A97, 0x11A97, + 0x11C2F, 0x11C2F, + 0x11C3E, 0x11C3E, + 0x11CA9, 0x11CA9, + 0x11CB1, 0x11CB1, + 0x11CB4, 0x11CB4, + 0x11D8A, 0x11D8E, + 0x11D93, 0x11D94, + 0x11D96, 0x11D96, + 0x11EF5, 0x11EF6, + 0x11F03, 0x11F03, + 0x11F34, 0x11F35, + 0x11F3E, 0x11F3F, + 0x11F41, 0x11F41, + 0x16F51, 0x16F87, + 0x16FF0, 0x16FF1, + 0x1D165, 0x1D166, + 0x1D16D, 0x1D172, +} + +@(rodata) +nonspacing_mark_ranges := [?]i32 { + 0x0300, 0x036F, + 0x0483, 0x0487, + 0x0591, 0x05BD, + 0x05BF, 0x05BF, + 0x05C1, 0x05C2, + 0x05C4, 0x05C5, + 0x05C7, 0x05C7, + 0x0610, 0x061A, + 0x064B, 0x065F, + 0x0670, 0x0670, + 0x06D6, 0x06DC, + 0x06DF, 0x06E4, + 0x06E7, 0x06E8, + 0x06EA, 0x06ED, + 0x0711, 0x0711, + 0x0730, 0x074A, + 0x07A6, 0x07B0, + 0x07EB, 0x07F3, + 0x07FD, 0x07FD, + 0x0816, 0x0819, + 0x081B, 0x0823, + 0x0825, 0x0827, + 0x0829, 0x082D, + 0x0859, 0x085B, + 0x0898, 0x089F, + 0x08CA, 0x08E1, + 0x08E3, 0x0902, + 0x093A, 0x093A, + 0x093C, 0x093C, + 0x0941, 0x0948, + 0x094D, 0x094D, + 0x0951, 0x0957, + 0x0962, 0x0963, + 0x0981, 0x0981, + 0x09BC, 0x09BC, + 0x09C1, 0x09C4, + 0x09CD, 0x09CD, + 0x09E2, 0x09E3, + 0x09FE, 0x09FE, + 0x0A01, 0x0A02, + 0x0A3C, 0x0A3C, + 0x0A41, 0x0A42, + 0x0A47, 0x0A48, + 0x0A4B, 0x0A4D, + 0x0A51, 0x0A51, + 0x0A70, 0x0A71, + 0x0A75, 0x0A75, + 0x0A81, 0x0A82, + 0x0ABC, 0x0ABC, + 0x0AC1, 0x0AC5, + 0x0AC7, 0x0AC8, + 0x0ACD, 0x0ACD, + 0x0AE2, 0x0AE3, + 0x0AFA, 0x0AFF, + 0x0B01, 0x0B01, + 0x0B3C, 0x0B3C, + 0x0B3F, 0x0B3F, + 0x0B41, 0x0B44, + 0x0B4D, 0x0B4D, + 0x0B55, 0x0B56, + 0x0B62, 0x0B63, + 0x0B82, 0x0B82, + 0x0BC0, 0x0BC0, + 0x0BCD, 0x0BCD, + 0x0C00, 0x0C00, + 0x0C04, 0x0C04, + 0x0C3C, 0x0C3C, + 0x0C3E, 0x0C40, + 0x0C46, 0x0C48, + 0x0C4A, 0x0C4D, + 0x0C55, 0x0C56, + 0x0C62, 0x0C63, + 0x0C81, 0x0C81, + 0x0CBC, 0x0CBC, + 0x0CBF, 0x0CBF, + 0x0CC6, 0x0CC6, + 0x0CCC, 0x0CCD, + 0x0CE2, 0x0CE3, + 0x0D00, 0x0D01, + 0x0D3B, 0x0D3C, + 0x0D41, 0x0D44, + 0x0D4D, 0x0D4D, + 0x0D62, 0x0D63, + 0x0D81, 0x0D81, + 0x0DCA, 0x0DCA, + 0x0DD2, 0x0DD4, + 0x0DD6, 0x0DD6, + 0x0E31, 0x0E31, + 0x0E34, 0x0E3A, + 0x0E47, 0x0E4E, + 0x0EB1, 0x0EB1, + 0x0EB4, 0x0EBC, + 0x0EC8, 0x0ECE, + 0x0F18, 0x0F19, + 0x0F35, 0x0F35, + 0x0F37, 0x0F37, + 0x0F39, 0x0F39, + 0x0F71, 0x0F7E, + 0x0F80, 0x0F84, + 0x0F86, 0x0F87, + 0x0F8D, 0x0F97, + 0x0F99, 0x0FBC, + 0x0FC6, 0x0FC6, + 0x102D, 0x1030, + 0x1032, 0x1037, + 0x1039, 0x103A, + 0x103D, 0x103E, + 0x1058, 0x1059, + 0x105E, 0x1060, + 0x1071, 0x1074, + 0x1082, 0x1082, + 0x1085, 0x1086, + 0x108D, 0x108D, + 0x109D, 0x109D, + 0x135D, 0x135F, + 0x1712, 0x1714, + 0x1732, 0x1733, + 0x1752, 0x1753, + 0x1772, 0x1773, + 0x17B4, 0x17B5, + 0x17B7, 0x17BD, + 0x17C6, 0x17C6, + 0x17C9, 0x17D3, + 0x17DD, 0x17DD, + 0x180B, 0x180D, + 0x180F, 0x180F, + 0x1885, 0x1886, + 0x18A9, 0x18A9, + 0x1920, 0x1922, + 0x1927, 0x1928, + 0x1932, 0x1932, + 0x1939, 0x193B, + 0x1A17, 0x1A18, + 0x1A1B, 0x1A1B, + 0x1A56, 0x1A56, + 0x1A58, 0x1A5E, + 0x1A60, 0x1A60, + 0x1A62, 0x1A62, + 0x1A65, 0x1A6C, + 0x1A73, 0x1A7C, + 0x1A7F, 0x1A7F, + 0x1AB0, 0x1ABD, + 0x1ABF, 0x1ACE, + 0x1B00, 0x1B03, + 0x1B34, 0x1B34, + 0x1B36, 0x1B3A, + 0x1B3C, 0x1B3C, + 0x1B42, 0x1B42, + 0x1B6B, 0x1B73, + 0x1B80, 0x1B81, + 0x1BA2, 0x1BA5, + 0x1BA8, 0x1BA9, + 0x1BAB, 0x1BAD, + 0x1BE6, 0x1BE6, + 0x1BE8, 0x1BE9, + 0x1BED, 0x1BED, + 0x1BEF, 0x1BF1, + 0x1C2C, 0x1C33, + 0x1C36, 0x1C37, + 0x1CD0, 0x1CD2, + 0x1CD4, 0x1CE0, + 0x1CE2, 0x1CE8, + 0x1CED, 0x1CED, + 0x1CF4, 0x1CF4, + 0x1CF8, 0x1CF9, + 0x1DC0, 0x1DFF, + 0x20D0, 0x20DC, + 0x20E1, 0x20E1, + 0x20E5, 0x20F0, + 0x2CEF, 0x2CF1, + 0x2D7F, 0x2D7F, + 0x2DE0, 0x2DFF, + 0x302A, 0x302D, + 0x3099, 0x309A, + 0xA66F, 0xA66F, + 0xA674, 0xA67D, + 0xA69E, 0xA69F, + 0xA6F0, 0xA6F1, + 0xA802, 0xA802, + 0xA806, 0xA806, + 0xA80B, 0xA80B, + 0xA825, 0xA826, + 0xA82C, 0xA82C, + 0xA8C4, 0xA8C5, + 0xA8E0, 0xA8F1, + 0xA8FF, 0xA8FF, + 0xA926, 0xA92D, + 0xA947, 0xA951, + 0xA980, 0xA982, + 0xA9B3, 0xA9B3, + 0xA9B6, 0xA9B9, + 0xA9BC, 0xA9BD, + 0xA9E5, 0xA9E5, + 0xAA29, 0xAA2E, + 0xAA31, 0xAA32, + 0xAA35, 0xAA36, + 0xAA43, 0xAA43, + 0xAA4C, 0xAA4C, + 0xAA7C, 0xAA7C, + 0xAAB0, 0xAAB0, + 0xAAB2, 0xAAB4, + 0xAAB7, 0xAAB8, + 0xAABE, 0xAABF, + 0xAAC1, 0xAAC1, + 0xAAEC, 0xAAED, + 0xAAF6, 0xAAF6, + 0xABE5, 0xABE5, + 0xABE8, 0xABE8, + 0xABED, 0xABED, + 0xFB1E, 0xFB1E, + 0xFE00, 0xFE0F, + 0xFE20, 0xFE2F, + 0x101FD, 0x101FD, + 0x102E0, 0x102E0, + 0x10376, 0x1037A, + 0x10A01, 0x10A03, + 0x10A05, 0x10A06, + 0x10A0C, 0x10A0F, + 0x10A38, 0x10A3A, + 0x10A3F, 0x10A3F, + 0x10AE5, 0x10AE6, + 0x10D24, 0x10D27, + 0x10EAB, 0x10EAC, + 0x10EFD, 0x10EFF, + 0x10F46, 0x10F50, + 0x10F82, 0x10F85, + 0x11001, 0x11001, + 0x11038, 0x11046, + 0x11070, 0x11070, + 0x11073, 0x11074, + 0x1107F, 0x11081, + 0x110B3, 0x110B6, + 0x110B9, 0x110BA, + 0x110C2, 0x110C2, + 0x11100, 0x11102, + 0x11127, 0x1112B, + 0x1112D, 0x11134, + 0x11173, 0x11173, + 0x11180, 0x11181, + 0x111B6, 0x111BE, + 0x111C9, 0x111CC, + 0x111CF, 0x111CF, + 0x1122F, 0x11231, + 0x11234, 0x11234, + 0x11236, 0x11237, + 0x1123E, 0x1123E, + 0x11241, 0x11241, + 0x112DF, 0x112DF, + 0x112E3, 0x112EA, + 0x11300, 0x11301, + 0x1133B, 0x1133C, + 0x11340, 0x11340, + 0x11366, 0x1136C, + 0x11370, 0x11374, + 0x11438, 0x1143F, + 0x11442, 0x11444, + 0x11446, 0x11446, + 0x1145E, 0x1145E, + 0x114B3, 0x114B8, + 0x114BA, 0x114BA, + 0x114BF, 0x114C0, + 0x114C2, 0x114C3, + 0x115B2, 0x115B5, + 0x115BC, 0x115BD, + 0x115BF, 0x115C0, + 0x115DC, 0x115DD, + 0x11633, 0x1163A, + 0x1163D, 0x1163D, + 0x1163F, 0x11640, + 0x116AB, 0x116AB, + 0x116AD, 0x116AD, + 0x116B0, 0x116B5, + 0x116B7, 0x116B7, + 0x1171D, 0x1171F, + 0x11722, 0x11725, + 0x11727, 0x1172B, + 0x1182F, 0x11837, + 0x11839, 0x1183A, + 0x1193B, 0x1193C, + 0x1193E, 0x1193E, + 0x11943, 0x11943, + 0x119D4, 0x119D7, + 0x119DA, 0x119DB, + 0x119E0, 0x119E0, + 0x11A01, 0x11A0A, + 0x11A33, 0x11A38, + 0x11A3B, 0x11A3E, + 0x11A47, 0x11A47, + 0x11A51, 0x11A56, + 0x11A59, 0x11A5B, + 0x11A8A, 0x11A96, + 0x11A98, 0x11A99, + 0x11C30, 0x11C36, + 0x11C38, 0x11C3D, + 0x11C3F, 0x11C3F, + 0x11C92, 0x11CA7, + 0x11CAA, 0x11CB0, + 0x11CB2, 0x11CB3, + 0x11CB5, 0x11CB6, + 0x11D31, 0x11D36, + 0x11D3A, 0x11D3A, + 0x11D3C, 0x11D3D, + 0x11D3F, 0x11D45, + 0x11D47, 0x11D47, + 0x11D90, 0x11D91, + 0x11D95, 0x11D95, + 0x11D97, 0x11D97, + 0x11EF3, 0x11EF4, + 0x11F00, 0x11F01, + 0x11F36, 0x11F3A, + 0x11F40, 0x11F40, + 0x11F42, 0x11F42, + 0x13440, 0x13440, + 0x13447, 0x13455, + 0x16AF0, 0x16AF4, + 0x16B30, 0x16B36, + 0x16F4F, 0x16F4F, + 0x16F8F, 0x16F92, + 0x16FE4, 0x16FE4, + 0x1BC9D, 0x1BC9E, + 0x1CF00, 0x1CF2D, + 0x1CF30, 0x1CF46, + 0x1D167, 0x1D169, + 0x1D17B, 0x1D182, + 0x1D185, 0x1D18B, + 0x1D1AA, 0x1D1AD, + 0x1D242, 0x1D244, + 0x1DA00, 0x1DA36, + 0x1DA3B, 0x1DA6C, + 0x1DA75, 0x1DA75, + 0x1DA84, 0x1DA84, + 0x1DA9B, 0x1DA9F, + 0x1DAA1, 0x1DAAF, + 0x1E000, 0x1E006, + 0x1E008, 0x1E018, + 0x1E01B, 0x1E021, + 0x1E023, 0x1E024, + 0x1E026, 0x1E02A, + 0x1E08F, 0x1E08F, + 0x1E130, 0x1E136, + 0x1E2AE, 0x1E2AE, + 0x1E2EC, 0x1E2EF, + 0x1E4EC, 0x1E4EF, + 0x1E8D0, 0x1E8D6, + 0x1E944, 0x1E94A, + 0xE0100, 0xE01EF, +} + +@(rodata) +emoji_extended_pictographic_ranges := [?]i32 { + 0x00A9, 0x00A9, + 0x00AE, 0x00AE, + 0x203C, 0x203C, + 0x2049, 0x2049, + 0x2122, 0x2122, + 0x2139, 0x2139, + 0x2194, 0x2199, + 0x21A9, 0x21AA, + 0x231A, 0x231B, + 0x2328, 0x2328, + 0x2388, 0x2388, + 0x23CF, 0x23CF, + 0x23E9, 0x23EC, + 0x23ED, 0x23EE, + 0x23EF, 0x23EF, + 0x23F0, 0x23F0, + 0x23F1, 0x23F2, + 0x23F3, 0x23F3, + 0x23F8, 0x23FA, + 0x24C2, 0x24C2, + 0x25AA, 0x25AB, + 0x25B6, 0x25B6, + 0x25C0, 0x25C0, + 0x25FB, 0x25FE, + 0x2600, 0x2601, + 0x2602, 0x2603, + 0x2604, 0x2604, + 0x2605, 0x2605, + 0x2607, 0x260D, + 0x260E, 0x260E, + 0x260F, 0x2610, + 0x2611, 0x2611, + 0x2612, 0x2612, + 0x2614, 0x2615, + 0x2616, 0x2617, + 0x2618, 0x2618, + 0x2619, 0x261C, + 0x261D, 0x261D, + 0x261E, 0x261F, + 0x2620, 0x2620, + 0x2621, 0x2621, + 0x2622, 0x2623, + 0x2624, 0x2625, + 0x2626, 0x2626, + 0x2627, 0x2629, + 0x262A, 0x262A, + 0x262B, 0x262D, + 0x262E, 0x262E, + 0x262F, 0x262F, + 0x2630, 0x2637, + 0x2638, 0x2639, + 0x263A, 0x263A, + 0x263B, 0x263F, + 0x2640, 0x2640, + 0x2641, 0x2641, + 0x2642, 0x2642, + 0x2643, 0x2647, + 0x2648, 0x2653, + 0x2654, 0x265E, + 0x265F, 0x265F, + 0x2660, 0x2660, + 0x2661, 0x2662, + 0x2663, 0x2663, + 0x2664, 0x2664, + 0x2665, 0x2666, + 0x2667, 0x2667, + 0x2668, 0x2668, + 0x2669, 0x267A, + 0x267B, 0x267B, + 0x267C, 0x267D, + 0x267E, 0x267E, + 0x267F, 0x267F, + 0x2680, 0x2685, + 0x2690, 0x2691, + 0x2692, 0x2692, + 0x2693, 0x2693, + 0x2694, 0x2694, + 0x2695, 0x2695, + 0x2696, 0x2697, + 0x2698, 0x2698, + 0x2699, 0x2699, + 0x269A, 0x269A, + 0x269B, 0x269C, + 0x269D, 0x269F, + 0x26A0, 0x26A1, + 0x26A2, 0x26A6, + 0x26A7, 0x26A7, + 0x26A8, 0x26A9, + 0x26AA, 0x26AB, + 0x26AC, 0x26AF, + 0x26B0, 0x26B1, + 0x26B2, 0x26BC, + 0x26BD, 0x26BE, + 0x26BF, 0x26C3, + 0x26C4, 0x26C5, + 0x26C6, 0x26C7, + 0x26C8, 0x26C8, + 0x26C9, 0x26CD, + 0x26CE, 0x26CE, + 0x26CF, 0x26CF, + 0x26D0, 0x26D0, + 0x26D1, 0x26D1, + 0x26D2, 0x26D2, + 0x26D3, 0x26D3, + 0x26D4, 0x26D4, + 0x26D5, 0x26E8, + 0x26E9, 0x26E9, + 0x26EA, 0x26EA, + 0x26EB, 0x26EF, + 0x26F0, 0x26F1, + 0x26F2, 0x26F3, + 0x26F4, 0x26F4, + 0x26F5, 0x26F5, + 0x26F6, 0x26F6, + 0x26F7, 0x26F9, + 0x26FA, 0x26FA, + 0x26FB, 0x26FC, + 0x26FD, 0x26FD, + 0x26FE, 0x2701, + 0x2702, 0x2702, + 0x2703, 0x2704, + 0x2705, 0x2705, + 0x2708, 0x270C, + 0x270D, 0x270D, + 0x270E, 0x270E, + 0x270F, 0x270F, + 0x2710, 0x2711, + 0x2712, 0x2712, + 0x2714, 0x2714, + 0x2716, 0x2716, + 0x271D, 0x271D, + 0x2721, 0x2721, + 0x2728, 0x2728, + 0x2733, 0x2734, + 0x2744, 0x2744, + 0x2747, 0x2747, + 0x274C, 0x274C, + 0x274E, 0x274E, + 0x2753, 0x2755, + 0x2757, 0x2757, + 0x2763, 0x2763, + 0x2764, 0x2764, + 0x2765, 0x2767, + 0x2795, 0x2797, + 0x27A1, 0x27A1, + 0x27B0, 0x27B0, + 0x27BF, 0x27BF, + 0x2934, 0x2935, + 0x2B05, 0x2B07, + 0x2B1B, 0x2B1C, + 0x2B50, 0x2B50, + 0x2B55, 0x2B55, + 0x3030, 0x3030, + 0x303D, 0x303D, + 0x3297, 0x3297, + 0x3299, 0x3299, + 0x1F000, 0x1F003, + 0x1F004, 0x1F004, + 0x1F005, 0x1F0CE, + 0x1F0CF, 0x1F0CF, + 0x1F0D0, 0x1F0FF, + 0x1F10D, 0x1F10F, + 0x1F12F, 0x1F12F, + 0x1F16C, 0x1F16F, + 0x1F170, 0x1F171, + 0x1F17E, 0x1F17F, + 0x1F18E, 0x1F18E, + 0x1F191, 0x1F19A, + 0x1F1AD, 0x1F1E5, + 0x1F201, 0x1F202, + 0x1F203, 0x1F20F, + 0x1F21A, 0x1F21A, + 0x1F22F, 0x1F22F, + 0x1F232, 0x1F23A, + 0x1F23C, 0x1F23F, + 0x1F249, 0x1F24F, + 0x1F250, 0x1F251, + 0x1F252, 0x1F2FF, + 0x1F300, 0x1F30C, + 0x1F30D, 0x1F30E, + 0x1F30F, 0x1F30F, + 0x1F310, 0x1F310, + 0x1F311, 0x1F311, + 0x1F312, 0x1F312, + 0x1F313, 0x1F315, + 0x1F316, 0x1F318, + 0x1F319, 0x1F319, + 0x1F31A, 0x1F31A, + 0x1F31B, 0x1F31B, + 0x1F31C, 0x1F31C, + 0x1F31D, 0x1F31E, + 0x1F31F, 0x1F320, + 0x1F321, 0x1F321, + 0x1F322, 0x1F323, + 0x1F324, 0x1F32C, + 0x1F32D, 0x1F32F, + 0x1F330, 0x1F331, + 0x1F332, 0x1F333, + 0x1F334, 0x1F335, + 0x1F336, 0x1F336, + 0x1F337, 0x1F34A, + 0x1F34B, 0x1F34B, + 0x1F34C, 0x1F34F, + 0x1F350, 0x1F350, + 0x1F351, 0x1F37B, + 0x1F37C, 0x1F37C, + 0x1F37D, 0x1F37D, + 0x1F37E, 0x1F37F, + 0x1F380, 0x1F393, + 0x1F394, 0x1F395, + 0x1F396, 0x1F397, + 0x1F398, 0x1F398, + 0x1F399, 0x1F39B, + 0x1F39C, 0x1F39D, + 0x1F39E, 0x1F39F, + 0x1F3A0, 0x1F3C4, + 0x1F3C5, 0x1F3C5, + 0x1F3C6, 0x1F3C6, + 0x1F3C7, 0x1F3C7, + 0x1F3C8, 0x1F3C8, + 0x1F3C9, 0x1F3C9, + 0x1F3CA, 0x1F3CA, + 0x1F3CB, 0x1F3CE, + 0x1F3CF, 0x1F3D3, + 0x1F3D4, 0x1F3DF, + 0x1F3E0, 0x1F3E3, + 0x1F3E4, 0x1F3E4, + 0x1F3E5, 0x1F3F0, + 0x1F3F1, 0x1F3F2, + 0x1F3F3, 0x1F3F3, + 0x1F3F4, 0x1F3F4, + 0x1F3F5, 0x1F3F5, + 0x1F3F6, 0x1F3F6, + 0x1F3F7, 0x1F3F7, + 0x1F3F8, 0x1F3FA, + 0x1F400, 0x1F407, + 0x1F408, 0x1F408, + 0x1F409, 0x1F40B, + 0x1F40C, 0x1F40E, + 0x1F40F, 0x1F410, + 0x1F411, 0x1F412, + 0x1F413, 0x1F413, + 0x1F414, 0x1F414, + 0x1F415, 0x1F415, + 0x1F416, 0x1F416, + 0x1F417, 0x1F429, + 0x1F42A, 0x1F42A, + 0x1F42B, 0x1F43E, + 0x1F43F, 0x1F43F, + 0x1F440, 0x1F440, + 0x1F441, 0x1F441, + 0x1F442, 0x1F464, + 0x1F465, 0x1F465, + 0x1F466, 0x1F46B, + 0x1F46C, 0x1F46D, + 0x1F46E, 0x1F4AC, + 0x1F4AD, 0x1F4AD, + 0x1F4AE, 0x1F4B5, + 0x1F4B6, 0x1F4B7, + 0x1F4B8, 0x1F4EB, + 0x1F4EC, 0x1F4ED, + 0x1F4EE, 0x1F4EE, + 0x1F4EF, 0x1F4EF, + 0x1F4F0, 0x1F4F4, + 0x1F4F5, 0x1F4F5, + 0x1F4F6, 0x1F4F7, + 0x1F4F8, 0x1F4F8, + 0x1F4F9, 0x1F4FC, + 0x1F4FD, 0x1F4FD, + 0x1F4FE, 0x1F4FE, + 0x1F4FF, 0x1F502, + 0x1F503, 0x1F503, + 0x1F504, 0x1F507, + 0x1F508, 0x1F508, + 0x1F509, 0x1F509, + 0x1F50A, 0x1F514, + 0x1F515, 0x1F515, + 0x1F516, 0x1F52B, + 0x1F52C, 0x1F52D, + 0x1F52E, 0x1F53D, + 0x1F546, 0x1F548, + 0x1F549, 0x1F54A, + 0x1F54B, 0x1F54E, + 0x1F54F, 0x1F54F, + 0x1F550, 0x1F55B, + 0x1F55C, 0x1F567, + 0x1F568, 0x1F56E, + 0x1F56F, 0x1F570, + 0x1F571, 0x1F572, + 0x1F573, 0x1F579, + 0x1F57A, 0x1F57A, + 0x1F57B, 0x1F586, + 0x1F587, 0x1F587, + 0x1F588, 0x1F589, + 0x1F58A, 0x1F58D, + 0x1F58E, 0x1F58F, + 0x1F590, 0x1F590, + 0x1F591, 0x1F594, + 0x1F595, 0x1F596, + 0x1F597, 0x1F5A3, + 0x1F5A4, 0x1F5A4, + 0x1F5A5, 0x1F5A5, + 0x1F5A6, 0x1F5A7, + 0x1F5A8, 0x1F5A8, + 0x1F5A9, 0x1F5B0, + 0x1F5B1, 0x1F5B2, + 0x1F5B3, 0x1F5BB, + 0x1F5BC, 0x1F5BC, + 0x1F5BD, 0x1F5C1, + 0x1F5C2, 0x1F5C4, + 0x1F5C5, 0x1F5D0, + 0x1F5D1, 0x1F5D3, + 0x1F5D4, 0x1F5DB, + 0x1F5DC, 0x1F5DE, + 0x1F5DF, 0x1F5E0, + 0x1F5E1, 0x1F5E1, + 0x1F5E2, 0x1F5E2, + 0x1F5E3, 0x1F5E3, + 0x1F5E4, 0x1F5E7, + 0x1F5E8, 0x1F5E8, + 0x1F5E9, 0x1F5EE, + 0x1F5EF, 0x1F5EF, + 0x1F5F0, 0x1F5F2, + 0x1F5F3, 0x1F5F3, + 0x1F5F4, 0x1F5F9, + 0x1F5FA, 0x1F5FA, + 0x1F5FB, 0x1F5FF, + 0x1F600, 0x1F600, + 0x1F601, 0x1F606, + 0x1F607, 0x1F608, + 0x1F609, 0x1F60D, + 0x1F60E, 0x1F60E, + 0x1F60F, 0x1F60F, + 0x1F610, 0x1F610, + 0x1F611, 0x1F611, + 0x1F612, 0x1F614, + 0x1F615, 0x1F615, + 0x1F616, 0x1F616, + 0x1F617, 0x1F617, + 0x1F618, 0x1F618, + 0x1F619, 0x1F619, + 0x1F61A, 0x1F61A, + 0x1F61B, 0x1F61B, + 0x1F61C, 0x1F61E, + 0x1F61F, 0x1F61F, + 0x1F620, 0x1F625, + 0x1F626, 0x1F627, + 0x1F628, 0x1F62B, + 0x1F62C, 0x1F62C, + 0x1F62D, 0x1F62D, + 0x1F62E, 0x1F62F, + 0x1F630, 0x1F633, + 0x1F634, 0x1F634, + 0x1F635, 0x1F635, + 0x1F636, 0x1F636, + 0x1F637, 0x1F640, + 0x1F641, 0x1F644, + 0x1F645, 0x1F64F, + 0x1F680, 0x1F680, + 0x1F681, 0x1F682, + 0x1F683, 0x1F685, + 0x1F686, 0x1F686, + 0x1F687, 0x1F687, + 0x1F688, 0x1F688, + 0x1F689, 0x1F689, + 0x1F68A, 0x1F68B, + 0x1F68C, 0x1F68C, + 0x1F68D, 0x1F68D, + 0x1F68E, 0x1F68E, + 0x1F68F, 0x1F68F, + 0x1F690, 0x1F690, + 0x1F691, 0x1F693, + 0x1F694, 0x1F694, + 0x1F695, 0x1F695, + 0x1F696, 0x1F696, + 0x1F697, 0x1F697, + 0x1F698, 0x1F698, + 0x1F699, 0x1F69A, + 0x1F69B, 0x1F6A1, + 0x1F6A2, 0x1F6A2, + 0x1F6A3, 0x1F6A3, + 0x1F6A4, 0x1F6A5, + 0x1F6A6, 0x1F6A6, + 0x1F6A7, 0x1F6AD, + 0x1F6AE, 0x1F6B1, + 0x1F6B2, 0x1F6B2, + 0x1F6B3, 0x1F6B5, + 0x1F6B6, 0x1F6B6, + 0x1F6B7, 0x1F6B8, + 0x1F6B9, 0x1F6BE, + 0x1F6BF, 0x1F6BF, + 0x1F6C0, 0x1F6C0, + 0x1F6C1, 0x1F6C5, + 0x1F6C6, 0x1F6CA, + 0x1F6CB, 0x1F6CB, + 0x1F6CC, 0x1F6CC, + 0x1F6CD, 0x1F6CF, + 0x1F6D0, 0x1F6D0, + 0x1F6D1, 0x1F6D2, + 0x1F6D3, 0x1F6D4, + 0x1F6D5, 0x1F6D5, + 0x1F6D6, 0x1F6D7, + 0x1F6D8, 0x1F6DB, + 0x1F6DC, 0x1F6DC, + 0x1F6DD, 0x1F6DF, + 0x1F6E0, 0x1F6E5, + 0x1F6E6, 0x1F6E8, + 0x1F6E9, 0x1F6E9, + 0x1F6EA, 0x1F6EA, + 0x1F6EB, 0x1F6EC, + 0x1F6ED, 0x1F6EF, + 0x1F6F0, 0x1F6F0, + 0x1F6F1, 0x1F6F2, + 0x1F6F3, 0x1F6F3, + 0x1F6F4, 0x1F6F6, + 0x1F6F7, 0x1F6F8, + 0x1F6F9, 0x1F6F9, + 0x1F6FA, 0x1F6FA, + 0x1F6FB, 0x1F6FC, + 0x1F6FD, 0x1F6FF, + 0x1F774, 0x1F77F, + 0x1F7D5, 0x1F7DF, + 0x1F7E0, 0x1F7EB, + 0x1F7EC, 0x1F7EF, + 0x1F7F0, 0x1F7F0, + 0x1F7F1, 0x1F7FF, + 0x1F80C, 0x1F80F, + 0x1F848, 0x1F84F, + 0x1F85A, 0x1F85F, + 0x1F888, 0x1F88F, + 0x1F8AE, 0x1F8FF, + 0x1F90C, 0x1F90C, + 0x1F90D, 0x1F90F, + 0x1F910, 0x1F918, + 0x1F919, 0x1F91E, + 0x1F91F, 0x1F91F, + 0x1F920, 0x1F927, + 0x1F928, 0x1F92F, + 0x1F930, 0x1F930, + 0x1F931, 0x1F932, + 0x1F933, 0x1F93A, + 0x1F93C, 0x1F93E, + 0x1F93F, 0x1F93F, + 0x1F940, 0x1F945, + 0x1F947, 0x1F94B, + 0x1F94C, 0x1F94C, + 0x1F94D, 0x1F94F, + 0x1F950, 0x1F95E, + 0x1F95F, 0x1F96B, + 0x1F96C, 0x1F970, + 0x1F971, 0x1F971, + 0x1F972, 0x1F972, + 0x1F973, 0x1F976, + 0x1F977, 0x1F978, + 0x1F979, 0x1F979, + 0x1F97A, 0x1F97A, + 0x1F97B, 0x1F97B, + 0x1F97C, 0x1F97F, + 0x1F980, 0x1F984, + 0x1F985, 0x1F991, + 0x1F992, 0x1F997, + 0x1F998, 0x1F9A2, + 0x1F9A3, 0x1F9A4, + 0x1F9A5, 0x1F9AA, + 0x1F9AB, 0x1F9AD, + 0x1F9AE, 0x1F9AF, + 0x1F9B0, 0x1F9B9, + 0x1F9BA, 0x1F9BF, + 0x1F9C0, 0x1F9C0, + 0x1F9C1, 0x1F9C2, + 0x1F9C3, 0x1F9CA, + 0x1F9CB, 0x1F9CB, + 0x1F9CC, 0x1F9CC, + 0x1F9CD, 0x1F9CF, + 0x1F9D0, 0x1F9E6, + 0x1F9E7, 0x1F9FF, + 0x1FA00, 0x1FA6F, + 0x1FA70, 0x1FA73, + 0x1FA74, 0x1FA74, + 0x1FA75, 0x1FA77, + 0x1FA78, 0x1FA7A, + 0x1FA7B, 0x1FA7C, + 0x1FA7D, 0x1FA7F, + 0x1FA80, 0x1FA82, + 0x1FA83, 0x1FA86, + 0x1FA87, 0x1FA88, + 0x1FA89, 0x1FA8F, + 0x1FA90, 0x1FA95, + 0x1FA96, 0x1FAA8, + 0x1FAA9, 0x1FAAC, + 0x1FAAD, 0x1FAAF, + 0x1FAB0, 0x1FAB6, + 0x1FAB7, 0x1FABA, + 0x1FABB, 0x1FABD, + 0x1FABE, 0x1FABE, + 0x1FABF, 0x1FABF, + 0x1FAC0, 0x1FAC2, + 0x1FAC3, 0x1FAC5, + 0x1FAC6, 0x1FACD, + 0x1FACE, 0x1FACF, + 0x1FAD0, 0x1FAD6, + 0x1FAD7, 0x1FAD9, + 0x1FADA, 0x1FADB, + 0x1FADC, 0x1FADF, + 0x1FAE0, 0x1FAE7, + 0x1FAE8, 0x1FAE8, + 0x1FAE9, 0x1FAEF, + 0x1FAF0, 0x1FAF6, + 0x1FAF7, 0x1FAF8, + 0x1FAF9, 0x1FAFF, + 0x1FC00, 0x1FFFD, +} + +@(rodata) +grapheme_extend_ranges := [?]i32 { + 0x0300, 0x036F, + 0x0483, 0x0487, + 0x0488, 0x0489, + 0x0591, 0x05BD, + 0x05BF, 0x05BF, + 0x05C1, 0x05C2, + 0x05C4, 0x05C5, + 0x05C7, 0x05C7, + 0x0610, 0x061A, + 0x064B, 0x065F, + 0x0670, 0x0670, + 0x06D6, 0x06DC, + 0x06DF, 0x06E4, + 0x06E7, 0x06E8, + 0x06EA, 0x06ED, + 0x0711, 0x0711, + 0x0730, 0x074A, + 0x07A6, 0x07B0, + 0x07EB, 0x07F3, + 0x07FD, 0x07FD, + 0x0816, 0x0819, + 0x081B, 0x0823, + 0x0825, 0x0827, + 0x0829, 0x082D, + 0x0859, 0x085B, + 0x0898, 0x089F, + 0x08CA, 0x08E1, + 0x08E3, 0x0902, + 0x093A, 0x093A, + 0x093C, 0x093C, + 0x0941, 0x0948, + 0x094D, 0x094D, + 0x0951, 0x0957, + 0x0962, 0x0963, + 0x0981, 0x0981, + 0x09BC, 0x09BC, + 0x09BE, 0x09BE, + 0x09C1, 0x09C4, + 0x09CD, 0x09CD, + 0x09D7, 0x09D7, + 0x09E2, 0x09E3, + 0x09FE, 0x09FE, + 0x0A01, 0x0A02, + 0x0A3C, 0x0A3C, + 0x0A41, 0x0A42, + 0x0A47, 0x0A48, + 0x0A4B, 0x0A4D, + 0x0A51, 0x0A51, + 0x0A70, 0x0A71, + 0x0A75, 0x0A75, + 0x0A81, 0x0A82, + 0x0ABC, 0x0ABC, + 0x0AC1, 0x0AC5, + 0x0AC7, 0x0AC8, + 0x0ACD, 0x0ACD, + 0x0AE2, 0x0AE3, + 0x0AFA, 0x0AFF, + 0x0B01, 0x0B01, + 0x0B3C, 0x0B3C, + 0x0B3E, 0x0B3E, + 0x0B3F, 0x0B3F, + 0x0B41, 0x0B44, + 0x0B4D, 0x0B4D, + 0x0B55, 0x0B56, + 0x0B57, 0x0B57, + 0x0B62, 0x0B63, + 0x0B82, 0x0B82, + 0x0BBE, 0x0BBE, + 0x0BC0, 0x0BC0, + 0x0BCD, 0x0BCD, + 0x0BD7, 0x0BD7, + 0x0C00, 0x0C00, + 0x0C04, 0x0C04, + 0x0C3C, 0x0C3C, + 0x0C3E, 0x0C40, + 0x0C46, 0x0C48, + 0x0C4A, 0x0C4D, + 0x0C55, 0x0C56, + 0x0C62, 0x0C63, + 0x0C81, 0x0C81, + 0x0CBC, 0x0CBC, + 0x0CBF, 0x0CBF, + 0x0CC2, 0x0CC2, + 0x0CC6, 0x0CC6, + 0x0CCC, 0x0CCD, + 0x0CD5, 0x0CD6, + 0x0CE2, 0x0CE3, + 0x0D00, 0x0D01, + 0x0D3B, 0x0D3C, + 0x0D3E, 0x0D3E, + 0x0D41, 0x0D44, + 0x0D4D, 0x0D4D, + 0x0D57, 0x0D57, + 0x0D62, 0x0D63, + 0x0D81, 0x0D81, + 0x0DCA, 0x0DCA, + 0x0DCF, 0x0DCF, + 0x0DD2, 0x0DD4, + 0x0DD6, 0x0DD6, + 0x0DDF, 0x0DDF, + 0x0E31, 0x0E31, + 0x0E34, 0x0E3A, + 0x0E47, 0x0E4E, + 0x0EB1, 0x0EB1, + 0x0EB4, 0x0EBC, + 0x0EC8, 0x0ECE, + 0x0F18, 0x0F19, + 0x0F35, 0x0F35, + 0x0F37, 0x0F37, + 0x0F39, 0x0F39, + 0x0F71, 0x0F7E, + 0x0F80, 0x0F84, + 0x0F86, 0x0F87, + 0x0F8D, 0x0F97, + 0x0F99, 0x0FBC, + 0x0FC6, 0x0FC6, + 0x102D, 0x1030, + 0x1032, 0x1037, + 0x1039, 0x103A, + 0x103D, 0x103E, + 0x1058, 0x1059, + 0x105E, 0x1060, + 0x1071, 0x1074, + 0x1082, 0x1082, + 0x1085, 0x1086, + 0x108D, 0x108D, + 0x109D, 0x109D, + 0x135D, 0x135F, + 0x1712, 0x1714, + 0x1732, 0x1733, + 0x1752, 0x1753, + 0x1772, 0x1773, + 0x17B4, 0x17B5, + 0x17B7, 0x17BD, + 0x17C6, 0x17C6, + 0x17C9, 0x17D3, + 0x17DD, 0x17DD, + 0x180B, 0x180D, + 0x180F, 0x180F, + 0x1885, 0x1886, + 0x18A9, 0x18A9, + 0x1920, 0x1922, + 0x1927, 0x1928, + 0x1932, 0x1932, + 0x1939, 0x193B, + 0x1A17, 0x1A18, + 0x1A1B, 0x1A1B, + 0x1A56, 0x1A56, + 0x1A58, 0x1A5E, + 0x1A60, 0x1A60, + 0x1A62, 0x1A62, + 0x1A65, 0x1A6C, + 0x1A73, 0x1A7C, + 0x1A7F, 0x1A7F, + 0x1AB0, 0x1ABD, + 0x1ABE, 0x1ABE, + 0x1ABF, 0x1ACE, + 0x1B00, 0x1B03, + 0x1B34, 0x1B34, + 0x1B35, 0x1B35, + 0x1B36, 0x1B3A, + 0x1B3C, 0x1B3C, + 0x1B42, 0x1B42, + 0x1B6B, 0x1B73, + 0x1B80, 0x1B81, + 0x1BA2, 0x1BA5, + 0x1BA8, 0x1BA9, + 0x1BAB, 0x1BAD, + 0x1BE6, 0x1BE6, + 0x1BE8, 0x1BE9, + 0x1BED, 0x1BED, + 0x1BEF, 0x1BF1, + 0x1C2C, 0x1C33, + 0x1C36, 0x1C37, + 0x1CD0, 0x1CD2, + 0x1CD4, 0x1CE0, + 0x1CE2, 0x1CE8, + 0x1CED, 0x1CED, + 0x1CF4, 0x1CF4, + 0x1CF8, 0x1CF9, + 0x1DC0, 0x1DFF, + 0x200C, 0x200C, + 0x20D0, 0x20DC, + 0x20DD, 0x20E0, + 0x20E1, 0x20E1, + 0x20E2, 0x20E4, + 0x20E5, 0x20F0, + 0x2CEF, 0x2CF1, + 0x2D7F, 0x2D7F, + 0x2DE0, 0x2DFF, + 0x302A, 0x302D, + 0x302E, 0x302F, + 0x3099, 0x309A, + 0xA66F, 0xA66F, + 0xA670, 0xA672, + 0xA674, 0xA67D, + 0xA69E, 0xA69F, + 0xA6F0, 0xA6F1, + 0xA802, 0xA802, + 0xA806, 0xA806, + 0xA80B, 0xA80B, + 0xA825, 0xA826, + 0xA82C, 0xA82C, + 0xA8C4, 0xA8C5, + 0xA8E0, 0xA8F1, + 0xA8FF, 0xA8FF, + 0xA926, 0xA92D, + 0xA947, 0xA951, + 0xA980, 0xA982, + 0xA9B3, 0xA9B3, + 0xA9B6, 0xA9B9, + 0xA9BC, 0xA9BD, + 0xA9E5, 0xA9E5, + 0xAA29, 0xAA2E, + 0xAA31, 0xAA32, + 0xAA35, 0xAA36, + 0xAA43, 0xAA43, + 0xAA4C, 0xAA4C, + 0xAA7C, 0xAA7C, + 0xAAB0, 0xAAB0, + 0xAAB2, 0xAAB4, + 0xAAB7, 0xAAB8, + 0xAABE, 0xAABF, + 0xAAC1, 0xAAC1, + 0xAAEC, 0xAAED, + 0xAAF6, 0xAAF6, + 0xABE5, 0xABE5, + 0xABE8, 0xABE8, + 0xABED, 0xABED, + 0xFB1E, 0xFB1E, + 0xFE00, 0xFE0F, + 0xFE20, 0xFE2F, + 0xFF9E, 0xFF9F, + 0x101FD, 0x101FD, + 0x102E0, 0x102E0, + 0x10376, 0x1037A, + 0x10A01, 0x10A03, + 0x10A05, 0x10A06, + 0x10A0C, 0x10A0F, + 0x10A38, 0x10A3A, + 0x10A3F, 0x10A3F, + 0x10AE5, 0x10AE6, + 0x10D24, 0x10D27, + 0x10EAB, 0x10EAC, + 0x10EFD, 0x10EFF, + 0x10F46, 0x10F50, + 0x10F82, 0x10F85, + 0x11001, 0x11001, + 0x11038, 0x11046, + 0x11070, 0x11070, + 0x11073, 0x11074, + 0x1107F, 0x11081, + 0x110B3, 0x110B6, + 0x110B9, 0x110BA, + 0x110C2, 0x110C2, + 0x11100, 0x11102, + 0x11127, 0x1112B, + 0x1112D, 0x11134, + 0x11173, 0x11173, + 0x11180, 0x11181, + 0x111B6, 0x111BE, + 0x111C9, 0x111CC, + 0x111CF, 0x111CF, + 0x1122F, 0x11231, + 0x11234, 0x11234, + 0x11236, 0x11237, + 0x1123E, 0x1123E, + 0x11241, 0x11241, + 0x112DF, 0x112DF, + 0x112E3, 0x112EA, + 0x11300, 0x11301, + 0x1133B, 0x1133C, + 0x1133E, 0x1133E, + 0x11340, 0x11340, + 0x11357, 0x11357, + 0x11366, 0x1136C, + 0x11370, 0x11374, + 0x11438, 0x1143F, + 0x11442, 0x11444, + 0x11446, 0x11446, + 0x1145E, 0x1145E, + 0x114B0, 0x114B0, + 0x114B3, 0x114B8, + 0x114BA, 0x114BA, + 0x114BD, 0x114BD, + 0x114BF, 0x114C0, + 0x114C2, 0x114C3, + 0x115AF, 0x115AF, + 0x115B2, 0x115B5, + 0x115BC, 0x115BD, + 0x115BF, 0x115C0, + 0x115DC, 0x115DD, + 0x11633, 0x1163A, + 0x1163D, 0x1163D, + 0x1163F, 0x11640, + 0x116AB, 0x116AB, + 0x116AD, 0x116AD, + 0x116B0, 0x116B5, + 0x116B7, 0x116B7, + 0x1171D, 0x1171F, + 0x11722, 0x11725, + 0x11727, 0x1172B, + 0x1182F, 0x11837, + 0x11839, 0x1183A, + 0x11930, 0x11930, + 0x1193B, 0x1193C, + 0x1193E, 0x1193E, + 0x11943, 0x11943, + 0x119D4, 0x119D7, + 0x119DA, 0x119DB, + 0x119E0, 0x119E0, + 0x11A01, 0x11A0A, + 0x11A33, 0x11A38, + 0x11A3B, 0x11A3E, + 0x11A47, 0x11A47, + 0x11A51, 0x11A56, + 0x11A59, 0x11A5B, + 0x11A8A, 0x11A96, + 0x11A98, 0x11A99, + 0x11C30, 0x11C36, + 0x11C38, 0x11C3D, + 0x11C3F, 0x11C3F, + 0x11C92, 0x11CA7, + 0x11CAA, 0x11CB0, + 0x11CB2, 0x11CB3, + 0x11CB5, 0x11CB6, + 0x11D31, 0x11D36, + 0x11D3A, 0x11D3A, + 0x11D3C, 0x11D3D, + 0x11D3F, 0x11D45, + 0x11D47, 0x11D47, + 0x11D90, 0x11D91, + 0x11D95, 0x11D95, + 0x11D97, 0x11D97, + 0x11EF3, 0x11EF4, + 0x11F00, 0x11F01, + 0x11F36, 0x11F3A, + 0x11F40, 0x11F40, + 0x11F42, 0x11F42, + 0x13440, 0x13440, + 0x13447, 0x13455, + 0x16AF0, 0x16AF4, + 0x16B30, 0x16B36, + 0x16F4F, 0x16F4F, + 0x16F8F, 0x16F92, + 0x16FE4, 0x16FE4, + 0x1BC9D, 0x1BC9E, + 0x1CF00, 0x1CF2D, + 0x1CF30, 0x1CF46, + 0x1D165, 0x1D165, + 0x1D167, 0x1D169, + 0x1D16E, 0x1D172, + 0x1D17B, 0x1D182, + 0x1D185, 0x1D18B, + 0x1D1AA, 0x1D1AD, + 0x1D242, 0x1D244, + 0x1DA00, 0x1DA36, + 0x1DA3B, 0x1DA6C, + 0x1DA75, 0x1DA75, + 0x1DA84, 0x1DA84, + 0x1DA9B, 0x1DA9F, + 0x1DAA1, 0x1DAAF, + 0x1E000, 0x1E006, + 0x1E008, 0x1E018, + 0x1E01B, 0x1E021, + 0x1E023, 0x1E024, + 0x1E026, 0x1E02A, + 0x1E08F, 0x1E08F, + 0x1E130, 0x1E136, + 0x1E2AE, 0x1E2AE, + 0x1E2EC, 0x1E2EF, + 0x1E4EC, 0x1E4EF, + 0x1E8D0, 0x1E8D6, + 0x1E944, 0x1E94A, + 0xE0020, 0xE007F, + 0xE0100, 0xE01EF, +} + +@(rodata) +hangul_syllable_lv_singlets := [?]i32 { + 0xAC00, + 0xAC1C, + 0xAC38, + 0xAC54, + 0xAC70, + 0xAC8C, + 0xACA8, + 0xACC4, + 0xACE0, + 0xACFC, + 0xAD18, + 0xAD34, + 0xAD50, + 0xAD6C, + 0xAD88, + 0xADA4, + 0xADC0, + 0xADDC, + 0xADF8, + 0xAE14, + 0xAE30, + 0xAE4C, + 0xAE68, + 0xAE84, + 0xAEA0, + 0xAEBC, + 0xAED8, + 0xAEF4, + 0xAF10, + 0xAF2C, + 0xAF48, + 0xAF64, + 0xAF80, + 0xAF9C, + 0xAFB8, + 0xAFD4, + 0xAFF0, + 0xB00C, + 0xB028, + 0xB044, + 0xB060, + 0xB07C, + 0xB098, + 0xB0B4, + 0xB0D0, + 0xB0EC, + 0xB108, + 0xB124, + 0xB140, + 0xB15C, + 0xB178, + 0xB194, + 0xB1B0, + 0xB1CC, + 0xB1E8, + 0xB204, + 0xB220, + 0xB23C, + 0xB258, + 0xB274, + 0xB290, + 0xB2AC, + 0xB2C8, + 0xB2E4, + 0xB300, + 0xB31C, + 0xB338, + 0xB354, + 0xB370, + 0xB38C, + 0xB3A8, + 0xB3C4, + 0xB3E0, + 0xB3FC, + 0xB418, + 0xB434, + 0xB450, + 0xB46C, + 0xB488, + 0xB4A4, + 0xB4C0, + 0xB4DC, + 0xB4F8, + 0xB514, + 0xB530, + 0xB54C, + 0xB568, + 0xB584, + 0xB5A0, + 0xB5BC, + 0xB5D8, + 0xB5F4, + 0xB610, + 0xB62C, + 0xB648, + 0xB664, + 0xB680, + 0xB69C, + 0xB6B8, + 0xB6D4, + 0xB6F0, + 0xB70C, + 0xB728, + 0xB744, + 0xB760, + 0xB77C, + 0xB798, + 0xB7B4, + 0xB7D0, + 0xB7EC, + 0xB808, + 0xB824, + 0xB840, + 0xB85C, + 0xB878, + 0xB894, + 0xB8B0, + 0xB8CC, + 0xB8E8, + 0xB904, + 0xB920, + 0xB93C, + 0xB958, + 0xB974, + 0xB990, + 0xB9AC, + 0xB9C8, + 0xB9E4, + 0xBA00, + 0xBA1C, + 0xBA38, + 0xBA54, + 0xBA70, + 0xBA8C, + 0xBAA8, + 0xBAC4, + 0xBAE0, + 0xBAFC, + 0xBB18, + 0xBB34, + 0xBB50, + 0xBB6C, + 0xBB88, + 0xBBA4, + 0xBBC0, + 0xBBDC, + 0xBBF8, + 0xBC14, + 0xBC30, + 0xBC4C, + 0xBC68, + 0xBC84, + 0xBCA0, + 0xBCBC, + 0xBCD8, + 0xBCF4, + 0xBD10, + 0xBD2C, + 0xBD48, + 0xBD64, + 0xBD80, + 0xBD9C, + 0xBDB8, + 0xBDD4, + 0xBDF0, + 0xBE0C, + 0xBE28, + 0xBE44, + 0xBE60, + 0xBE7C, + 0xBE98, + 0xBEB4, + 0xBED0, + 0xBEEC, + 0xBF08, + 0xBF24, + 0xBF40, + 0xBF5C, + 0xBF78, + 0xBF94, + 0xBFB0, + 0xBFCC, + 0xBFE8, + 0xC004, + 0xC020, + 0xC03C, + 0xC058, + 0xC074, + 0xC090, + 0xC0AC, + 0xC0C8, + 0xC0E4, + 0xC100, + 0xC11C, + 0xC138, + 0xC154, + 0xC170, + 0xC18C, + 0xC1A8, + 0xC1C4, + 0xC1E0, + 0xC1FC, + 0xC218, + 0xC234, + 0xC250, + 0xC26C, + 0xC288, + 0xC2A4, + 0xC2C0, + 0xC2DC, + 0xC2F8, + 0xC314, + 0xC330, + 0xC34C, + 0xC368, + 0xC384, + 0xC3A0, + 0xC3BC, + 0xC3D8, + 0xC3F4, + 0xC410, + 0xC42C, + 0xC448, + 0xC464, + 0xC480, + 0xC49C, + 0xC4B8, + 0xC4D4, + 0xC4F0, + 0xC50C, + 0xC528, + 0xC544, + 0xC560, + 0xC57C, + 0xC598, + 0xC5B4, + 0xC5D0, + 0xC5EC, + 0xC608, + 0xC624, + 0xC640, + 0xC65C, + 0xC678, + 0xC694, + 0xC6B0, + 0xC6CC, + 0xC6E8, + 0xC704, + 0xC720, + 0xC73C, + 0xC758, + 0xC774, + 0xC790, + 0xC7AC, + 0xC7C8, + 0xC7E4, + 0xC800, + 0xC81C, + 0xC838, + 0xC854, + 0xC870, + 0xC88C, + 0xC8A8, + 0xC8C4, + 0xC8E0, + 0xC8FC, + 0xC918, + 0xC934, + 0xC950, + 0xC96C, + 0xC988, + 0xC9A4, + 0xC9C0, + 0xC9DC, + 0xC9F8, + 0xCA14, + 0xCA30, + 0xCA4C, + 0xCA68, + 0xCA84, + 0xCAA0, + 0xCABC, + 0xCAD8, + 0xCAF4, + 0xCB10, + 0xCB2C, + 0xCB48, + 0xCB64, + 0xCB80, + 0xCB9C, + 0xCBB8, + 0xCBD4, + 0xCBF0, + 0xCC0C, + 0xCC28, + 0xCC44, + 0xCC60, + 0xCC7C, + 0xCC98, + 0xCCB4, + 0xCCD0, + 0xCCEC, + 0xCD08, + 0xCD24, + 0xCD40, + 0xCD5C, + 0xCD78, + 0xCD94, + 0xCDB0, + 0xCDCC, + 0xCDE8, + 0xCE04, + 0xCE20, + 0xCE3C, + 0xCE58, + 0xCE74, + 0xCE90, + 0xCEAC, + 0xCEC8, + 0xCEE4, + 0xCF00, + 0xCF1C, + 0xCF38, + 0xCF54, + 0xCF70, + 0xCF8C, + 0xCFA8, + 0xCFC4, + 0xCFE0, + 0xCFFC, + 0xD018, + 0xD034, + 0xD050, + 0xD06C, + 0xD088, + 0xD0A4, + 0xD0C0, + 0xD0DC, + 0xD0F8, + 0xD114, + 0xD130, + 0xD14C, + 0xD168, + 0xD184, + 0xD1A0, + 0xD1BC, + 0xD1D8, + 0xD1F4, + 0xD210, + 0xD22C, + 0xD248, + 0xD264, + 0xD280, + 0xD29C, + 0xD2B8, + 0xD2D4, + 0xD2F0, + 0xD30C, + 0xD328, + 0xD344, + 0xD360, + 0xD37C, + 0xD398, + 0xD3B4, + 0xD3D0, + 0xD3EC, + 0xD408, + 0xD424, + 0xD440, + 0xD45C, + 0xD478, + 0xD494, + 0xD4B0, + 0xD4CC, + 0xD4E8, + 0xD504, + 0xD520, + 0xD53C, + 0xD558, + 0xD574, + 0xD590, + 0xD5AC, + 0xD5C8, + 0xD5E4, + 0xD600, + 0xD61C, + 0xD638, + 0xD654, + 0xD670, + 0xD68C, + 0xD6A8, + 0xD6C4, + 0xD6E0, + 0xD6FC, + 0xD718, + 0xD734, + 0xD750, + 0xD76C, + 0xD788, +} + +@(rodata) +hangul_syllable_lvt_ranges := [?]i32 { + 0xAC01, 0xAC1B, + 0xAC1D, 0xAC37, + 0xAC39, 0xAC53, + 0xAC55, 0xAC6F, + 0xAC71, 0xAC8B, + 0xAC8D, 0xACA7, + 0xACA9, 0xACC3, + 0xACC5, 0xACDF, + 0xACE1, 0xACFB, + 0xACFD, 0xAD17, + 0xAD19, 0xAD33, + 0xAD35, 0xAD4F, + 0xAD51, 0xAD6B, + 0xAD6D, 0xAD87, + 0xAD89, 0xADA3, + 0xADA5, 0xADBF, + 0xADC1, 0xADDB, + 0xADDD, 0xADF7, + 0xADF9, 0xAE13, + 0xAE15, 0xAE2F, + 0xAE31, 0xAE4B, + 0xAE4D, 0xAE67, + 0xAE69, 0xAE83, + 0xAE85, 0xAE9F, + 0xAEA1, 0xAEBB, + 0xAEBD, 0xAED7, + 0xAED9, 0xAEF3, + 0xAEF5, 0xAF0F, + 0xAF11, 0xAF2B, + 0xAF2D, 0xAF47, + 0xAF49, 0xAF63, + 0xAF65, 0xAF7F, + 0xAF81, 0xAF9B, + 0xAF9D, 0xAFB7, + 0xAFB9, 0xAFD3, + 0xAFD5, 0xAFEF, + 0xAFF1, 0xB00B, + 0xB00D, 0xB027, + 0xB029, 0xB043, + 0xB045, 0xB05F, + 0xB061, 0xB07B, + 0xB07D, 0xB097, + 0xB099, 0xB0B3, + 0xB0B5, 0xB0CF, + 0xB0D1, 0xB0EB, + 0xB0ED, 0xB107, + 0xB109, 0xB123, + 0xB125, 0xB13F, + 0xB141, 0xB15B, + 0xB15D, 0xB177, + 0xB179, 0xB193, + 0xB195, 0xB1AF, + 0xB1B1, 0xB1CB, + 0xB1CD, 0xB1E7, + 0xB1E9, 0xB203, + 0xB205, 0xB21F, + 0xB221, 0xB23B, + 0xB23D, 0xB257, + 0xB259, 0xB273, + 0xB275, 0xB28F, + 0xB291, 0xB2AB, + 0xB2AD, 0xB2C7, + 0xB2C9, 0xB2E3, + 0xB2E5, 0xB2FF, + 0xB301, 0xB31B, + 0xB31D, 0xB337, + 0xB339, 0xB353, + 0xB355, 0xB36F, + 0xB371, 0xB38B, + 0xB38D, 0xB3A7, + 0xB3A9, 0xB3C3, + 0xB3C5, 0xB3DF, + 0xB3E1, 0xB3FB, + 0xB3FD, 0xB417, + 0xB419, 0xB433, + 0xB435, 0xB44F, + 0xB451, 0xB46B, + 0xB46D, 0xB487, + 0xB489, 0xB4A3, + 0xB4A5, 0xB4BF, + 0xB4C1, 0xB4DB, + 0xB4DD, 0xB4F7, + 0xB4F9, 0xB513, + 0xB515, 0xB52F, + 0xB531, 0xB54B, + 0xB54D, 0xB567, + 0xB569, 0xB583, + 0xB585, 0xB59F, + 0xB5A1, 0xB5BB, + 0xB5BD, 0xB5D7, + 0xB5D9, 0xB5F3, + 0xB5F5, 0xB60F, + 0xB611, 0xB62B, + 0xB62D, 0xB647, + 0xB649, 0xB663, + 0xB665, 0xB67F, + 0xB681, 0xB69B, + 0xB69D, 0xB6B7, + 0xB6B9, 0xB6D3, + 0xB6D5, 0xB6EF, + 0xB6F1, 0xB70B, + 0xB70D, 0xB727, + 0xB729, 0xB743, + 0xB745, 0xB75F, + 0xB761, 0xB77B, + 0xB77D, 0xB797, + 0xB799, 0xB7B3, + 0xB7B5, 0xB7CF, + 0xB7D1, 0xB7EB, + 0xB7ED, 0xB807, + 0xB809, 0xB823, + 0xB825, 0xB83F, + 0xB841, 0xB85B, + 0xB85D, 0xB877, + 0xB879, 0xB893, + 0xB895, 0xB8AF, + 0xB8B1, 0xB8CB, + 0xB8CD, 0xB8E7, + 0xB8E9, 0xB903, + 0xB905, 0xB91F, + 0xB921, 0xB93B, + 0xB93D, 0xB957, + 0xB959, 0xB973, + 0xB975, 0xB98F, + 0xB991, 0xB9AB, + 0xB9AD, 0xB9C7, + 0xB9C9, 0xB9E3, + 0xB9E5, 0xB9FF, + 0xBA01, 0xBA1B, + 0xBA1D, 0xBA37, + 0xBA39, 0xBA53, + 0xBA55, 0xBA6F, + 0xBA71, 0xBA8B, + 0xBA8D, 0xBAA7, + 0xBAA9, 0xBAC3, + 0xBAC5, 0xBADF, + 0xBAE1, 0xBAFB, + 0xBAFD, 0xBB17, + 0xBB19, 0xBB33, + 0xBB35, 0xBB4F, + 0xBB51, 0xBB6B, + 0xBB6D, 0xBB87, + 0xBB89, 0xBBA3, + 0xBBA5, 0xBBBF, + 0xBBC1, 0xBBDB, + 0xBBDD, 0xBBF7, + 0xBBF9, 0xBC13, + 0xBC15, 0xBC2F, + 0xBC31, 0xBC4B, + 0xBC4D, 0xBC67, + 0xBC69, 0xBC83, + 0xBC85, 0xBC9F, + 0xBCA1, 0xBCBB, + 0xBCBD, 0xBCD7, + 0xBCD9, 0xBCF3, + 0xBCF5, 0xBD0F, + 0xBD11, 0xBD2B, + 0xBD2D, 0xBD47, + 0xBD49, 0xBD63, + 0xBD65, 0xBD7F, + 0xBD81, 0xBD9B, + 0xBD9D, 0xBDB7, + 0xBDB9, 0xBDD3, + 0xBDD5, 0xBDEF, + 0xBDF1, 0xBE0B, + 0xBE0D, 0xBE27, + 0xBE29, 0xBE43, + 0xBE45, 0xBE5F, + 0xBE61, 0xBE7B, + 0xBE7D, 0xBE97, + 0xBE99, 0xBEB3, + 0xBEB5, 0xBECF, + 0xBED1, 0xBEEB, + 0xBEED, 0xBF07, + 0xBF09, 0xBF23, + 0xBF25, 0xBF3F, + 0xBF41, 0xBF5B, + 0xBF5D, 0xBF77, + 0xBF79, 0xBF93, + 0xBF95, 0xBFAF, + 0xBFB1, 0xBFCB, + 0xBFCD, 0xBFE7, + 0xBFE9, 0xC003, + 0xC005, 0xC01F, + 0xC021, 0xC03B, + 0xC03D, 0xC057, + 0xC059, 0xC073, + 0xC075, 0xC08F, + 0xC091, 0xC0AB, + 0xC0AD, 0xC0C7, + 0xC0C9, 0xC0E3, + 0xC0E5, 0xC0FF, + 0xC101, 0xC11B, + 0xC11D, 0xC137, + 0xC139, 0xC153, + 0xC155, 0xC16F, + 0xC171, 0xC18B, + 0xC18D, 0xC1A7, + 0xC1A9, 0xC1C3, + 0xC1C5, 0xC1DF, + 0xC1E1, 0xC1FB, + 0xC1FD, 0xC217, + 0xC219, 0xC233, + 0xC235, 0xC24F, + 0xC251, 0xC26B, + 0xC26D, 0xC287, + 0xC289, 0xC2A3, + 0xC2A5, 0xC2BF, + 0xC2C1, 0xC2DB, + 0xC2DD, 0xC2F7, + 0xC2F9, 0xC313, + 0xC315, 0xC32F, + 0xC331, 0xC34B, + 0xC34D, 0xC367, + 0xC369, 0xC383, + 0xC385, 0xC39F, + 0xC3A1, 0xC3BB, + 0xC3BD, 0xC3D7, + 0xC3D9, 0xC3F3, + 0xC3F5, 0xC40F, + 0xC411, 0xC42B, + 0xC42D, 0xC447, + 0xC449, 0xC463, + 0xC465, 0xC47F, + 0xC481, 0xC49B, + 0xC49D, 0xC4B7, + 0xC4B9, 0xC4D3, + 0xC4D5, 0xC4EF, + 0xC4F1, 0xC50B, + 0xC50D, 0xC527, + 0xC529, 0xC543, + 0xC545, 0xC55F, + 0xC561, 0xC57B, + 0xC57D, 0xC597, + 0xC599, 0xC5B3, + 0xC5B5, 0xC5CF, + 0xC5D1, 0xC5EB, + 0xC5ED, 0xC607, + 0xC609, 0xC623, + 0xC625, 0xC63F, + 0xC641, 0xC65B, + 0xC65D, 0xC677, + 0xC679, 0xC693, + 0xC695, 0xC6AF, + 0xC6B1, 0xC6CB, + 0xC6CD, 0xC6E7, + 0xC6E9, 0xC703, + 0xC705, 0xC71F, + 0xC721, 0xC73B, + 0xC73D, 0xC757, + 0xC759, 0xC773, + 0xC775, 0xC78F, + 0xC791, 0xC7AB, + 0xC7AD, 0xC7C7, + 0xC7C9, 0xC7E3, + 0xC7E5, 0xC7FF, + 0xC801, 0xC81B, + 0xC81D, 0xC837, + 0xC839, 0xC853, + 0xC855, 0xC86F, + 0xC871, 0xC88B, + 0xC88D, 0xC8A7, + 0xC8A9, 0xC8C3, + 0xC8C5, 0xC8DF, + 0xC8E1, 0xC8FB, + 0xC8FD, 0xC917, + 0xC919, 0xC933, + 0xC935, 0xC94F, + 0xC951, 0xC96B, + 0xC96D, 0xC987, + 0xC989, 0xC9A3, + 0xC9A5, 0xC9BF, + 0xC9C1, 0xC9DB, + 0xC9DD, 0xC9F7, + 0xC9F9, 0xCA13, + 0xCA15, 0xCA2F, + 0xCA31, 0xCA4B, + 0xCA4D, 0xCA67, + 0xCA69, 0xCA83, + 0xCA85, 0xCA9F, + 0xCAA1, 0xCABB, + 0xCABD, 0xCAD7, + 0xCAD9, 0xCAF3, + 0xCAF5, 0xCB0F, + 0xCB11, 0xCB2B, + 0xCB2D, 0xCB47, + 0xCB49, 0xCB63, + 0xCB65, 0xCB7F, + 0xCB81, 0xCB9B, + 0xCB9D, 0xCBB7, + 0xCBB9, 0xCBD3, + 0xCBD5, 0xCBEF, + 0xCBF1, 0xCC0B, + 0xCC0D, 0xCC27, + 0xCC29, 0xCC43, + 0xCC45, 0xCC5F, + 0xCC61, 0xCC7B, + 0xCC7D, 0xCC97, + 0xCC99, 0xCCB3, + 0xCCB5, 0xCCCF, + 0xCCD1, 0xCCEB, + 0xCCED, 0xCD07, + 0xCD09, 0xCD23, + 0xCD25, 0xCD3F, + 0xCD41, 0xCD5B, + 0xCD5D, 0xCD77, + 0xCD79, 0xCD93, + 0xCD95, 0xCDAF, + 0xCDB1, 0xCDCB, + 0xCDCD, 0xCDE7, + 0xCDE9, 0xCE03, + 0xCE05, 0xCE1F, + 0xCE21, 0xCE3B, + 0xCE3D, 0xCE57, + 0xCE59, 0xCE73, + 0xCE75, 0xCE8F, + 0xCE91, 0xCEAB, + 0xCEAD, 0xCEC7, + 0xCEC9, 0xCEE3, + 0xCEE5, 0xCEFF, + 0xCF01, 0xCF1B, + 0xCF1D, 0xCF37, + 0xCF39, 0xCF53, + 0xCF55, 0xCF6F, + 0xCF71, 0xCF8B, + 0xCF8D, 0xCFA7, + 0xCFA9, 0xCFC3, + 0xCFC5, 0xCFDF, + 0xCFE1, 0xCFFB, + 0xCFFD, 0xD017, + 0xD019, 0xD033, + 0xD035, 0xD04F, + 0xD051, 0xD06B, + 0xD06D, 0xD087, + 0xD089, 0xD0A3, + 0xD0A5, 0xD0BF, + 0xD0C1, 0xD0DB, + 0xD0DD, 0xD0F7, + 0xD0F9, 0xD113, + 0xD115, 0xD12F, + 0xD131, 0xD14B, + 0xD14D, 0xD167, + 0xD169, 0xD183, + 0xD185, 0xD19F, + 0xD1A1, 0xD1BB, + 0xD1BD, 0xD1D7, + 0xD1D9, 0xD1F3, + 0xD1F5, 0xD20F, + 0xD211, 0xD22B, + 0xD22D, 0xD247, + 0xD249, 0xD263, + 0xD265, 0xD27F, + 0xD281, 0xD29B, + 0xD29D, 0xD2B7, + 0xD2B9, 0xD2D3, + 0xD2D5, 0xD2EF, + 0xD2F1, 0xD30B, + 0xD30D, 0xD327, + 0xD329, 0xD343, + 0xD345, 0xD35F, + 0xD361, 0xD37B, + 0xD37D, 0xD397, + 0xD399, 0xD3B3, + 0xD3B5, 0xD3CF, + 0xD3D1, 0xD3EB, + 0xD3ED, 0xD407, + 0xD409, 0xD423, + 0xD425, 0xD43F, + 0xD441, 0xD45B, + 0xD45D, 0xD477, + 0xD479, 0xD493, + 0xD495, 0xD4AF, + 0xD4B1, 0xD4CB, + 0xD4CD, 0xD4E7, + 0xD4E9, 0xD503, + 0xD505, 0xD51F, + 0xD521, 0xD53B, + 0xD53D, 0xD557, + 0xD559, 0xD573, + 0xD575, 0xD58F, + 0xD591, 0xD5AB, + 0xD5AD, 0xD5C7, + 0xD5C9, 0xD5E3, + 0xD5E5, 0xD5FF, + 0xD601, 0xD61B, + 0xD61D, 0xD637, + 0xD639, 0xD653, + 0xD655, 0xD66F, + 0xD671, 0xD68B, + 0xD68D, 0xD6A7, + 0xD6A9, 0xD6C3, + 0xD6C5, 0xD6DF, + 0xD6E1, 0xD6FB, + 0xD6FD, 0xD717, + 0xD719, 0xD733, + 0xD735, 0xD74F, + 0xD751, 0xD76B, + 0xD76D, 0xD787, + 0xD789, 0xD7A3, +} + +@(rodata) +indic_conjunct_break_consonant_ranges := [?]i32 { + 0x0915, 0x0939, + 0x0958, 0x095F, + 0x0978, 0x097F, + 0x0995, 0x09A8, + 0x09AA, 0x09B0, + 0x09B2, 0x09B2, + 0x09B6, 0x09B9, + 0x09DC, 0x09DD, + 0x09DF, 0x09DF, + 0x09F0, 0x09F1, + 0x0A95, 0x0AA8, + 0x0AAA, 0x0AB0, + 0x0AB2, 0x0AB3, + 0x0AB5, 0x0AB9, + 0x0AF9, 0x0AF9, + 0x0B15, 0x0B28, + 0x0B2A, 0x0B30, + 0x0B32, 0x0B33, + 0x0B35, 0x0B39, + 0x0B5C, 0x0B5D, + 0x0B5F, 0x0B5F, + 0x0B71, 0x0B71, + 0x0C15, 0x0C28, + 0x0C2A, 0x0C39, + 0x0C58, 0x0C5A, + 0x0D15, 0x0D3A, +} + +@(rodata) +indic_conjunct_break_extend_ranges := [?]i32 { + 0x0300, 0x034E, + 0x0350, 0x036F, + 0x0483, 0x0487, + 0x0591, 0x05BD, + 0x05BF, 0x05BF, + 0x05C1, 0x05C2, + 0x05C4, 0x05C5, + 0x05C7, 0x05C7, + 0x0610, 0x061A, + 0x064B, 0x065F, + 0x0670, 0x0670, + 0x06D6, 0x06DC, + 0x06DF, 0x06E4, + 0x06E7, 0x06E8, + 0x06EA, 0x06ED, + 0x0711, 0x0711, + 0x0730, 0x074A, + 0x07EB, 0x07F3, + 0x07FD, 0x07FD, + 0x0816, 0x0819, + 0x081B, 0x0823, + 0x0825, 0x0827, + 0x0829, 0x082D, + 0x0859, 0x085B, + 0x0898, 0x089F, + 0x08CA, 0x08E1, + 0x08E3, 0x08FF, + 0x093C, 0x093C, + 0x0951, 0x0954, + 0x09BC, 0x09BC, + 0x09FE, 0x09FE, + 0x0A3C, 0x0A3C, + 0x0ABC, 0x0ABC, + 0x0B3C, 0x0B3C, + 0x0C3C, 0x0C3C, + 0x0C55, 0x0C56, + 0x0CBC, 0x0CBC, + 0x0D3B, 0x0D3C, + 0x0E38, 0x0E3A, + 0x0E48, 0x0E4B, + 0x0EB8, 0x0EBA, + 0x0EC8, 0x0ECB, + 0x0F18, 0x0F19, + 0x0F35, 0x0F35, + 0x0F37, 0x0F37, + 0x0F39, 0x0F39, + 0x0F71, 0x0F72, + 0x0F74, 0x0F74, + 0x0F7A, 0x0F7D, + 0x0F80, 0x0F80, + 0x0F82, 0x0F84, + 0x0F86, 0x0F87, + 0x0FC6, 0x0FC6, + 0x1037, 0x1037, + 0x1039, 0x103A, + 0x108D, 0x108D, + 0x135D, 0x135F, + 0x1714, 0x1714, + 0x17D2, 0x17D2, + 0x17DD, 0x17DD, + 0x18A9, 0x18A9, + 0x1939, 0x193B, + 0x1A17, 0x1A18, + 0x1A60, 0x1A60, + 0x1A75, 0x1A7C, + 0x1A7F, 0x1A7F, + 0x1AB0, 0x1ABD, + 0x1ABF, 0x1ACE, + 0x1B34, 0x1B34, + 0x1B6B, 0x1B73, + 0x1BAB, 0x1BAB, + 0x1BE6, 0x1BE6, + 0x1C37, 0x1C37, + 0x1CD0, 0x1CD2, + 0x1CD4, 0x1CE0, + 0x1CE2, 0x1CE8, + 0x1CED, 0x1CED, + 0x1CF4, 0x1CF4, + 0x1CF8, 0x1CF9, + 0x1DC0, 0x1DFF, + 0x200D, 0x200D, + 0x20D0, 0x20DC, + 0x20E1, 0x20E1, + 0x20E5, 0x20F0, + 0x2CEF, 0x2CF1, + 0x2D7F, 0x2D7F, + 0x2DE0, 0x2DFF, + 0x302A, 0x302D, + 0x302E, 0x302F, + 0x3099, 0x309A, + 0xA66F, 0xA66F, + 0xA674, 0xA67D, + 0xA69E, 0xA69F, + 0xA6F0, 0xA6F1, + 0xA82C, 0xA82C, + 0xA8E0, 0xA8F1, + 0xA92B, 0xA92D, + 0xA9B3, 0xA9B3, + 0xAAB0, 0xAAB0, + 0xAAB2, 0xAAB4, + 0xAAB7, 0xAAB8, + 0xAABE, 0xAABF, + 0xAAC1, 0xAAC1, + 0xAAF6, 0xAAF6, + 0xABED, 0xABED, + 0xFB1E, 0xFB1E, + 0xFE20, 0xFE2F, + 0x101FD, 0x101FD, + 0x102E0, 0x102E0, + 0x10376, 0x1037A, + 0x10A0D, 0x10A0D, + 0x10A0F, 0x10A0F, + 0x10A38, 0x10A3A, + 0x10A3F, 0x10A3F, + 0x10AE5, 0x10AE6, + 0x10D24, 0x10D27, + 0x10EAB, 0x10EAC, + 0x10EFD, 0x10EFF, + 0x10F46, 0x10F50, + 0x10F82, 0x10F85, + 0x11070, 0x11070, + 0x1107F, 0x1107F, + 0x110BA, 0x110BA, + 0x11100, 0x11102, + 0x11133, 0x11134, + 0x11173, 0x11173, + 0x111CA, 0x111CA, + 0x11236, 0x11236, + 0x112E9, 0x112EA, + 0x1133B, 0x1133C, + 0x11366, 0x1136C, + 0x11370, 0x11374, + 0x11446, 0x11446, + 0x1145E, 0x1145E, + 0x114C3, 0x114C3, + 0x115C0, 0x115C0, + 0x116B7, 0x116B7, + 0x1172B, 0x1172B, + 0x1183A, 0x1183A, + 0x1193E, 0x1193E, + 0x11943, 0x11943, + 0x11A34, 0x11A34, + 0x11A47, 0x11A47, + 0x11A99, 0x11A99, + 0x11D42, 0x11D42, + 0x11D44, 0x11D45, + 0x11D97, 0x11D97, + 0x11F42, 0x11F42, + 0x16AF0, 0x16AF4, + 0x16B30, 0x16B36, + 0x1BC9E, 0x1BC9E, + 0x1D165, 0x1D165, + 0x1D167, 0x1D169, + 0x1D16E, 0x1D172, + 0x1D17B, 0x1D182, + 0x1D185, 0x1D18B, + 0x1D1AA, 0x1D1AD, + 0x1D242, 0x1D244, + 0x1E000, 0x1E006, + 0x1E008, 0x1E018, + 0x1E01B, 0x1E021, + 0x1E023, 0x1E024, + 0x1E026, 0x1E02A, + 0x1E08F, 0x1E08F, + 0x1E130, 0x1E136, + 0x1E2AE, 0x1E2AE, + 0x1E2EC, 0x1E2EF, + 0x1E4EC, 0x1E4EF, + 0x1E8D0, 0x1E8D6, + 0x1E944, 0x1E94A, +} + +// Fullwidth (F) and Wide (W) are counted as 2. +// Everything else is 1. +// +// Derived from: https://unicode.org/Public/15.1.0/ucd/EastAsianWidth.txt +@(rodata) +normalized_east_asian_width_ranges := [?]i32 { + 0x0000, 0x10FF, 1, + 0x1100, 0x115F, 2, + 0x1160, 0x2319, 1, + 0x231A, 0x231B, 2, + 0x231C, 0x2328, 1, + 0x2329, 0x232A, 2, + 0x232B, 0x23E8, 1, + 0x23E9, 0x23EC, 2, + 0x23ED, 0x23EF, 1, + 0x23F0, 0x23F0, 2, + 0x23F1, 0x23F2, 1, + 0x23F3, 0x23F3, 2, + 0x23F4, 0x25FC, 1, + 0x25FD, 0x25FE, 2, + 0x25FF, 0x2613, 1, + 0x2614, 0x2615, 2, + 0x2616, 0x2647, 1, + 0x2648, 0x2653, 2, + 0x2654, 0x267E, 1, + 0x267F, 0x267F, 2, + 0x2680, 0x2692, 1, + 0x2693, 0x2693, 2, + 0x2694, 0x26A0, 1, + 0x26A1, 0x26A1, 2, + 0x26A2, 0x26A9, 1, + 0x26AA, 0x26AB, 2, + 0x26AC, 0x26BC, 1, + 0x26BD, 0x26BE, 2, + 0x26BF, 0x26C3, 1, + 0x26C4, 0x26C5, 2, + 0x26C6, 0x26CD, 1, + 0x26CE, 0x26CE, 2, + 0x26CF, 0x26D3, 1, + 0x26D4, 0x26D4, 2, + 0x26D5, 0x26E9, 1, + 0x26EA, 0x26EA, 2, + 0x26EB, 0x26F1, 1, + 0x26F2, 0x26F3, 2, + 0x26F4, 0x26F4, 1, + 0x26F5, 0x26F5, 2, + 0x26F6, 0x26F9, 1, + 0x26FA, 0x26FA, 2, + 0x26FB, 0x26FC, 1, + 0x26FD, 0x26FD, 2, + 0x26FE, 0x2704, 1, + 0x2705, 0x2705, 2, + 0x2706, 0x2709, 1, + 0x270A, 0x270B, 2, + 0x270C, 0x2727, 1, + 0x2728, 0x2728, 2, + 0x2729, 0x274B, 1, + 0x274C, 0x274C, 2, + 0x274D, 0x274D, 1, + 0x274E, 0x274E, 2, + 0x274F, 0x2752, 1, + 0x2753, 0x2755, 2, + 0x2756, 0x2756, 1, + 0x2757, 0x2757, 2, + 0x2758, 0x2794, 1, + 0x2795, 0x2797, 2, + 0x2798, 0x27AF, 1, + 0x27B0, 0x27B0, 2, + 0x27B1, 0x27BE, 1, + 0x27BF, 0x27BF, 2, + 0x27C0, 0x2B1A, 1, + 0x2B1B, 0x2B1C, 2, + 0x2B1D, 0x2B4F, 1, + 0x2B50, 0x2B50, 2, + 0x2B51, 0x2B54, 1, + 0x2B55, 0x2B55, 2, + 0x2B56, 0x2E5D, 1, + 0x2E80, 0x303E, 2, + 0x303F, 0x303F, 1, + 0x3041, 0x3247, 2, + 0x3248, 0x324F, 1, + 0x3250, 0x4DBF, 2, + 0x4DC0, 0x4DFF, 1, + 0x4E00, 0xA4C6, 2, + 0xA4D0, 0xA95F, 1, + 0xA960, 0xA97C, 2, + 0xA980, 0xABF9, 1, + 0xAC00, 0xD7A3, 2, + 0xD7B0, 0xF8FF, 1, + 0xF900, 0xFAFF, 2, + 0xFB00, 0xFE0F, 1, + 0xFE10, 0xFE19, 2, + 0xFE20, 0xFE2F, 1, + 0xFE30, 0xFE6B, 2, + 0xFE70, 0xFEFF, 1, + 0xFF01, 0xFF60, 2, + 0xFF61, 0xFFDC, 1, + 0xFFE0, 0xFFE6, 2, + 0xFFE8, 0x16F9F, 1, + 0x16FE0, 0x1B2FB, 2, + 0x1BC00, 0x1F003, 1, + 0x1F004, 0x1F004, 2, + 0x1F005, 0x1F0CE, 1, + 0x1F0CF, 0x1F0CF, 2, + 0x1F0D1, 0x1F18D, 1, + 0x1F18E, 0x1F18E, 2, + 0x1F18F, 0x1F190, 1, + 0x1F191, 0x1F19A, 2, + 0x1F19B, 0x1F1FF, 1, + 0x1F200, 0x1F320, 2, + 0x1F321, 0x1F32C, 1, + 0x1F32D, 0x1F335, 2, + 0x1F336, 0x1F336, 1, + 0x1F337, 0x1F37C, 2, + 0x1F37D, 0x1F37D, 1, + 0x1F37E, 0x1F393, 2, + 0x1F394, 0x1F39F, 1, + 0x1F3A0, 0x1F3CA, 2, + 0x1F3CB, 0x1F3CE, 1, + 0x1F3CF, 0x1F3D3, 2, + 0x1F3D4, 0x1F3DF, 1, + 0x1F3E0, 0x1F3F0, 2, + 0x1F3F1, 0x1F3F3, 1, + 0x1F3F4, 0x1F3F4, 2, + 0x1F3F5, 0x1F3F7, 1, + 0x1F3F8, 0x1F43E, 2, + 0x1F43F, 0x1F43F, 1, + 0x1F440, 0x1F440, 2, + 0x1F441, 0x1F441, 1, + 0x1F442, 0x1F4FC, 2, + 0x1F4FD, 0x1F4FE, 1, + 0x1F4FF, 0x1F53D, 2, + 0x1F53E, 0x1F54A, 1, + 0x1F54B, 0x1F54E, 2, + 0x1F54F, 0x1F54F, 1, + 0x1F550, 0x1F567, 2, + 0x1F568, 0x1F579, 1, + 0x1F57A, 0x1F57A, 2, + 0x1F57B, 0x1F594, 1, + 0x1F595, 0x1F596, 2, + 0x1F597, 0x1F5A3, 1, + 0x1F5A4, 0x1F5A4, 2, + 0x1F5A5, 0x1F5FA, 1, + 0x1F5FB, 0x1F64F, 2, + 0x1F650, 0x1F67F, 1, + 0x1F680, 0x1F6C5, 2, + 0x1F6C6, 0x1F6CB, 1, + 0x1F6CC, 0x1F6CC, 2, + 0x1F6CD, 0x1F6CF, 1, + 0x1F6D0, 0x1F6D2, 2, + 0x1F6D3, 0x1F6D4, 1, + 0x1F6D5, 0x1F6DF, 2, + 0x1F6E0, 0x1F6EA, 1, + 0x1F6EB, 0x1F6EC, 2, + 0x1F6F0, 0x1F6F3, 1, + 0x1F6F4, 0x1F6FC, 2, + 0x1F700, 0x1F7D9, 1, + 0x1F7E0, 0x1F7F0, 2, + 0x1F800, 0x1F90B, 1, + 0x1F90C, 0x1F93A, 2, + 0x1F93B, 0x1F93B, 1, + 0x1F93C, 0x1F945, 2, + 0x1F946, 0x1F946, 1, + 0x1F947, 0x1F9FF, 2, + 0x1FA00, 0x1FA6D, 1, + 0x1FA70, 0x1FAF8, 2, + 0x1FB00, 0x1FBF9, 1, + 0x20000, 0x3FFFD, 2, + 0xE0001, 0x10FFFD, 1, +} + +// +// End of Unicode 15.1.0 block. +// diff --git a/core/unicode/tools/generate_entity_table.odin b/core/unicode/tools/generate_entity_table.odin index ec8106cbe..16baa1adf 100644 --- a/core/unicode/tools/generate_entity_table.odin +++ b/core/unicode/tools/generate_entity_table.odin @@ -142,7 +142,7 @@ generate_encoding_entity_table :: proc() { /* Generate table. */ - fmt.wprintln(w, "package unicode_entity") + fmt.wprintln(w, "package encoding_unicode_entity") fmt.wprintln(w, "") fmt.wprintln(w, GENERATED) fmt.wprintln(w, "") @@ -195,12 +195,12 @@ named_xml_entity_to_rune :: proc(name: string) -> (decoded: rune, ok: bool) { e := entity_map[v] - fmt.wprintf(w, "\t\t\tcase \"%v\":", e.name) + fmt.wprintf(w, "\t\tcase \"%v\":", e.name) for i := len(e.name); i < max_name_length; i += 1 { fmt.wprintf(w, " ") } fmt.wprintf(w, " // %v\n", e.description) - fmt.wprintf(w, "\t\t\t\treturn %v, true\n", rune_to_string(e.codepoint)) + fmt.wprintf(w, "\t\t\treturn %v, true\n", rune_to_string(e.codepoint)) should_close = true } diff --git a/core/unicode/utf8/grapheme.odin b/core/unicode/utf8/grapheme.odin new file mode 100644 index 000000000..50d1789ab --- /dev/null +++ b/core/unicode/utf8/grapheme.odin @@ -0,0 +1,411 @@ +package utf8 + +import "core:unicode" + +ZERO_WIDTH_JOINER :: unicode.ZERO_WIDTH_JOINER +is_control :: unicode.is_control +is_hangul_syllable_leading :: unicode.is_hangul_syllable_leading +is_hangul_syllable_vowel :: unicode.is_hangul_syllable_vowel +is_hangul_syllable_trailing :: unicode.is_hangul_syllable_trailing +is_hangul_syllable_lv :: unicode.is_hangul_syllable_lv +is_hangul_syllable_lvt :: unicode.is_hangul_syllable_lvt +is_indic_conjunct_break_extend :: unicode.is_indic_conjunct_break_extend +is_indic_conjunct_break_linker :: unicode.is_indic_conjunct_break_linker +is_indic_conjunct_break_consonant :: unicode.is_indic_conjunct_break_consonant +is_gcb_extend_class :: unicode.is_gcb_extend_class +is_spacing_mark :: unicode.is_spacing_mark +is_gcb_prepend_class :: unicode.is_gcb_prepend_class +is_emoji_extended_pictographic :: unicode.is_emoji_extended_pictographic +is_regional_indicator :: unicode.is_regional_indicator +normalized_east_asian_width :: unicode.normalized_east_asian_width + + +Grapheme :: struct { + byte_index: int, + rune_index: int, + width: int, +} + +/* +Count the individual graphemes in a UTF-8 string. + +Inputs: +- str: The input string. + +Returns: +- graphemes: The number of graphemes in the string. +- runes: The number of runes in the string. +- width: The width of the string in number of monospace cells. +*/ +@(require_results) +grapheme_count :: proc(str: string) -> (graphemes, runes, width: int) { + _, graphemes, runes, width = decode_grapheme_clusters(str, false) + return +} + +/* +Decode the individual graphemes in a UTF-8 string. + +*Allocates Using Provided Allocator* + +Inputs: +- str: The input string. +- track_graphemes: Whether or not to allocate and return `graphemes` with extra data about each grapheme. +- allocator: (default: context.allocator) + +Returns: +- graphemes: Extra data about each grapheme. +- grapheme_count: The number of graphemes in the string. +- rune_count: The number of runes in the string. +- width: The width of the string in number of monospace cells. +*/ +@(require_results) +decode_grapheme_clusters :: proc( + str: string, + track_graphemes := true, + allocator := context.allocator, +) -> ( + graphemes: [dynamic]Grapheme, + grapheme_count: int, + rune_count: int, + width: int, +) { + // The following procedure implements text segmentation by breaking on + // Grapheme Cluster Boundaries[1], using the values[2] and rules[3] from + // the Unicode® Standard Annex #29, entitled: + // + // UNICODE TEXT SEGMENTATION + // + // Version: Unicode 15.1.0 + // Date: 2023-08-16 + // Revision: 43 + // + // This procedure is conformant[4] to UAX29-C1-1, otherwise known as the + // extended, non-legacy ruleset. + // + // Please see the references below for more information. + // + // + // NOTE(Feoramund): This procedure has not been highly optimized. + // A couple opportunities were taken to bypass repeated checking when a + // rune is outside of certain codepoint ranges, but little else has been + // done. Standard switches, conditionals, and binary search are used to + // see if a rune fits into a certain category. + // + // I did find that only one prior rune of state was necessary to build an + // algorithm that successfully passes all 4,835 test cases provided with + // this implementation from the Unicode organization's website. + // + // My initial implementation tracked explicit breaks and counted them once + // the string iteration had terminated. I've found this current + // implementation to be far simpler and need no allocations (unless the + // caller wants position data). + // + // Most rules work backwards instead of forwards which has helped keep this + // simple, despite its length and verbosity. + // + // + // The implementation has been left verbose and in the order described by + // the specification, to enable better readability and future upkeep. + // + // Some possible optimizations might include: + // + // - saving the type of `last_rune` instead of the exact rune. + // - reordering rules. + // - combining tables. + // + // + // [1]: https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries + // [2]: https://www.unicode.org/reports/tr29/#Default_Grapheme_Cluster_Table + // [3]: https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules + // [4]: https://www.unicode.org/reports/tr29/#Conformance + + // Additionally, this procedure now takes into account Standard Annex #11, + // in order to estimate how visually wide the string will appear on a + // monospaced display. This can only ever be a rough guess, as this tends + // to be an implementation detail relating to which fonts are being used, + // how codepoints are interpreted and drawn, if codepoint sequences are + // interpreted correctly, and et cetera. + // + // For example, a program may not properly interpret an emoji modifier + // sequence and print the component glyphs instead of one whole glyph. + // + // See here for more information: https://www.unicode.org/reports/tr11/ + // + // NOTE: There is no explicit mention of what to do with zero-width spaces + // as far as grapheme cluster segmentation goes, therefore this + // implementation may count and return graphemes with a `width` of zero. + // + // Treat them as any other space. + + Grapheme_Cluster_Sequence :: enum { + None, + Indic, + Emoji, + Regional, + } + + context.allocator = allocator + + last_rune: rune + last_rune_breaks_forward: bool + + last_width: int + last_grapheme_count: int + + bypass_next_rune: bool + + regional_indicator_counter: int + + current_sequence: Grapheme_Cluster_Sequence + continue_sequence: bool + + for this_rune, byte_index in str { + defer { + // "Break at the start and end of text, unless the text is empty." + // + // GB1: sot ÷ Any + // GB2: Any ÷ eot + if rune_count == 0 && grapheme_count == 0 { + grapheme_count += 1 + } + + if grapheme_count > last_grapheme_count { + width += normalized_east_asian_width(this_rune) + if track_graphemes { + append(&graphemes, Grapheme{ + byte_index, + rune_count, + width - last_width, + }) + } + last_grapheme_count = grapheme_count + last_width = width + } + + last_rune = this_rune + rune_count += 1 + + if !continue_sequence { + current_sequence = .None + regional_indicator_counter = 0 + } + continue_sequence = false + } + + // "Do not break between a CR and LF. Otherwise, break before and after controls." + // + // GB3: CR × LF + // GB4: (Control | CR | LF) ÷ + // GB5: ÷ (Control | CR | LF) + if this_rune == '\n' && last_rune == '\r' { + last_rune_breaks_forward = false + bypass_next_rune = false + continue + } + + if is_control(this_rune) { + grapheme_count += 1 + last_rune_breaks_forward = true + bypass_next_rune = true + continue + } + + // (This check is for rules that work forwards, instead of backwards.) + if bypass_next_rune { + if last_rune_breaks_forward { + grapheme_count += 1 + last_rune_breaks_forward = false + } + + bypass_next_rune = false + continue + } + + // (Optimization 1: Prevent low runes from proceeding further.) + // + // * 0xA9 and 0xAE are in the Extended_Pictographic range, + // which is checked later in GB11. + if this_rune != 0xA9 && this_rune != 0xAE && this_rune <= 0x2FF { + grapheme_count += 1 + continue + } + + // (Optimization 2: Check if the rune is in the Hangul space before getting specific.) + if 0x1100 <= this_rune && this_rune <= 0xD7FB { + // "Do not break Hangul syllable sequences." + // + // GB6: L × (L | V | LV | LVT) + // GB7: (LV | V) × (V | T) + // GB8: (LVT | T) × T + if is_hangul_syllable_leading(this_rune) || + is_hangul_syllable_lv(this_rune) || + is_hangul_syllable_lvt(this_rune) { + if !is_hangul_syllable_leading(last_rune) { + grapheme_count += 1 + } + continue + } + + if is_hangul_syllable_vowel(this_rune) { + if is_hangul_syllable_leading(last_rune) || + is_hangul_syllable_vowel(last_rune) || + is_hangul_syllable_lv(last_rune) { + continue + } + grapheme_count += 1 + continue + } + + if is_hangul_syllable_trailing(this_rune) { + if is_hangul_syllable_trailing(last_rune) || + is_hangul_syllable_lvt(last_rune) || + is_hangul_syllable_lv(last_rune) || + is_hangul_syllable_vowel(last_rune) { + continue + } + grapheme_count += 1 + continue + } + } + + // "Do not break before extending characters or ZWJ." + // + // GB9: × (Extend | ZWJ) + if this_rune == ZERO_WIDTH_JOINER { + continue_sequence = true + continue + } + + if is_gcb_extend_class(this_rune) { + // (Support for GB9c.) + if current_sequence == .Indic { + if is_indic_conjunct_break_extend(this_rune) && ( + is_indic_conjunct_break_linker(last_rune) || + is_indic_conjunct_break_consonant(last_rune) ) { + continue_sequence = true + continue + } + + if is_indic_conjunct_break_linker(this_rune) && ( + is_indic_conjunct_break_linker(last_rune) || + is_indic_conjunct_break_extend(last_rune) || + is_indic_conjunct_break_consonant(last_rune) ) { + continue_sequence = true + continue + } + + continue + } + + // (Support for GB11.) + if current_sequence == .Emoji && ( + is_gcb_extend_class(last_rune) || + is_emoji_extended_pictographic(last_rune) ) { + continue_sequence = true + } + + continue + } + + // _The GB9a and GB9b rules only apply to extended grapheme clusters:_ + // "Do not break before SpacingMarks, or after Prepend characters." + // + // GB9a: × SpacingMark + // GB9b: Prepend × + if is_spacing_mark(this_rune) { + continue + } + + if is_gcb_prepend_class(this_rune) { + grapheme_count += 1 + bypass_next_rune = true + continue + } + + // _The GB9c rule only applies to extended grapheme clusters:_ + // "Do not break within certain combinations with Indic_Conjunct_Break (InCB)=Linker." + // + // GB9c: \p{InCB=Consonant} [ \p{InCB=Extend} \p{InCB=Linker} ]* \p{InCB=Linker} [ \p{InCB=Extend} \p{InCB=Linker} ]* × \p{InCB=Consonant} + if is_indic_conjunct_break_consonant(this_rune) { + if current_sequence == .Indic { + if last_rune == ZERO_WIDTH_JOINER || + is_indic_conjunct_break_linker(last_rune) { + continue_sequence = true + } else { + grapheme_count += 1 + } + } else { + grapheme_count += 1 + current_sequence = .Indic + continue_sequence = true + } + continue + } + + if is_indic_conjunct_break_extend(this_rune) { + if current_sequence == .Indic { + if is_indic_conjunct_break_consonant(last_rune) || + is_indic_conjunct_break_linker(last_rune) { + continue_sequence = true + } else { + grapheme_count += 1 + } + } + continue + } + + if is_indic_conjunct_break_linker(this_rune) { + if current_sequence == .Indic { + if is_indic_conjunct_break_extend(last_rune) || + is_indic_conjunct_break_linker(last_rune) { + continue_sequence = true + } else { + grapheme_count += 1 + } + } + continue + } + + // + // (Curiously, there is no GB10.) + // + + // "Do not break within emoji modifier sequences or emoji zwj sequences." + // + // GB11: \p{Extended_Pictographic} Extend* ZWJ × \p{Extended_Pictographic} + if is_emoji_extended_pictographic(this_rune) { + if current_sequence != .Emoji || last_rune != ZERO_WIDTH_JOINER { + grapheme_count += 1 + } + current_sequence = .Emoji + continue_sequence = true + continue + } + + // "Do not break within emoji flag sequences. + // That is, do not break between regional indicator (RI) symbols + // if there is an odd number of RI characters before the break point." + // + // GB12: sot (RI RI)* RI × RI + // GB13: [^RI] (RI RI)* RI × RI + if is_regional_indicator(this_rune) { + if regional_indicator_counter & 1 == 0 { + grapheme_count += 1 + } + + current_sequence = .Regional + continue_sequence = true + regional_indicator_counter += 1 + + continue + } + + // "Otherwise, break everywhere." + // + // GB999: Any ÷ Any + grapheme_count += 1 + } + + return +} diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 3eebdf0b0..976a0f0e5 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -21,9 +21,11 @@ import queue "core:container/queue" import small_array "core:container/small_array" import lru "core:container/lru" import list "core:container/intrusive/list" +import rbtree "core:container/rbtree" import topological_sort "core:container/topological_sort" import crypto "core:crypto" +import aes "core:crypto/aes" import blake2b "core:crypto/blake2b" import blake2s "core:crypto/blake2s" import chacha20 "core:crypto/chacha20" @@ -47,25 +49,30 @@ import tuplehash "core:crypto/tuplehash" import x25519 "core:crypto/x25519" import pe "core:debug/pe" +import trace "core:debug/trace" import dynlib "core:dynlib" import net "core:net" +import ansi "core:encoding/ansi" import base32 "core:encoding/base32" import base64 "core:encoding/base64" +import cbor "core:encoding/cbor" import csv "core:encoding/csv" +import endian "core:encoding/endian" import hxa "core:encoding/hxa" import json "core:encoding/json" import varint "core:encoding/varint" import xml "core:encoding/xml" -import endian "core:encoding/endian" -import cbor "core:encoding/cbor" +import uuid "core:encoding/uuid" +import uuid_legacy "core:encoding/uuid/legacy" import fmt "core:fmt" import hash "core:hash" import xxhash "core:hash/xxhash" import image "core:image" +import bmp "core:image/bmp" import netpbm "core:image/netpbm" import png "core:image/png" import qoi "core:image/qoi" @@ -87,13 +94,12 @@ import ease "core:math/ease" import cmplx "core:math/cmplx" import mem "core:mem" +import tlsf "core:mem/tlsf" import virtual "core:mem/virtual" import ast "core:odin/ast" import doc_format "core:odin/doc-format" -import odin_format "core:odin/format" import odin_parser "core:odin/parser" -import odin_printer "core:odin/printer" import odin_tokenizer "core:odin/tokenizer" import spall "core:prof/spall" @@ -103,9 +109,12 @@ import os "core:os" import slashpath "core:path/slashpath" import filepath "core:path/filepath" +import relative "core:relative" + import reflect "core:reflect" import runtime "base:runtime" import simd "core:simd" +import x86 "core:simd/x86" import slice "core:slice" import slice_heap "core:slice/heap" import sort "core:sort" @@ -113,14 +122,17 @@ import strconv "core:strconv" import strings "core:strings" import sync "core:sync" import testing "core:testing" -import scanner "core:text/scanner" + +import edit "core:text/edit" import i18n "core:text/i18n" import match "core:text/match" +import scanner "core:text/scanner" import table "core:text/table" -import edit "core:text/edit" + import thread "core:thread" import time "core:time" import datetime "core:time/datetime" +import flags "core:flags" import sysinfo "core:sys/info" @@ -147,9 +159,11 @@ _ :: queue _ :: small_array _ :: lru _ :: list +_ :: rbtree _ :: topological_sort _ :: crypto _ :: crypto_hash +_ :: aes _ :: blake2b _ :: blake2s _ :: chacha20 @@ -171,8 +185,10 @@ _ :: sm3 _ :: tuplehash _ :: x25519 _ :: pe +_ :: trace _ :: dynlib _ :: net +_ :: ansi _ :: base32 _ :: base64 _ :: csv @@ -186,6 +202,7 @@ _ :: fmt _ :: hash _ :: xxhash _ :: image +_ :: bmp _ :: netpbm _ :: png _ :: qoi @@ -204,20 +221,21 @@ _ :: rand _ :: ease _ :: cmplx _ :: mem +_ :: tlsf _ :: virtual _ :: ast _ :: doc_format -_ :: odin_format _ :: odin_parser -_ :: odin_printer _ :: odin_tokenizer _ :: os _ :: spall _ :: slashpath _ :: filepath +_ :: relative _ :: reflect _ :: runtime _ :: simd +_ :: x86 _ :: slice _ :: slice_heap _ :: sort @@ -233,8 +251,11 @@ _ :: edit _ :: thread _ :: time _ :: datetime +_ :: flags _ :: sysinfo _ :: unicode +_ :: uuid +_ :: uuid_legacy _ :: utf8 _ :: utf8string _ :: utf16 diff --git a/examples/all/all_vendor_windows.odin b/examples/all/all_vendor_windows.odin new file mode 100644 index 000000000..0c72c886c --- /dev/null +++ b/examples/all/all_vendor_windows.odin @@ -0,0 +1,4 @@ +package all + +import wgpu "vendor:wgpu" +_ :: wgpu \ No newline at end of file diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 1f6d337e8..d31711bad 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -8,7 +8,7 @@ import "core:thread" import "core:time" import "core:reflect" import "base:runtime" -import "core:intrinsics" +import "base:intrinsics" import "core:math/big" /* @@ -48,7 +48,7 @@ the_basics :: proc() { // os.args holds the path to the current executable and any arguments passed to it. if len(os.args) == 1 { fmt.printf("Hellope from %v.\n", os.args[0]) - } else { + } else if len(os.args) > 2 { fmt.printf("%v, %v! from %v.\n", os.args[1], os.args[2], os.args[0]) } @@ -1140,6 +1140,7 @@ prefix_table := [?]string{ print_mutex := b64(false) +@(disabled=!thread.IS_SUPPORTED) threading_example :: proc() { fmt.println("\n# threading_example") @@ -2275,7 +2276,7 @@ arbitrary_precision_mathematics :: proc() { } fmt.printf(as) if print_extra_info { - fmt.printf(" (base: %v, bits: %v, digits: %v)", base, cb, a.used) + fmt.printf(" (base: %v, bits: %v, digits: %v)", base, cb, a.used) } if newline { fmt.println() @@ -2385,7 +2386,7 @@ matrix_type :: proc() { c := a * b #assert(type_of(c) == matrix[2, 2]f32) - fmt.tprintln("c = a * b", c) + fmt.println("c = a * b", c) } { // Matrices support multiplication between matrices and arrays @@ -2550,6 +2551,50 @@ matrix_type :: proc() { // matrix_minor(m) } +bit_field_type :: proc() { + fmt.println("\n# bit_field type") + // A `bit_field` is a record type in Odin that is akin to a bit-packed struct. + // IMPORTNAT NOTE: `bit_field` is NOT equivalent to `bit_set` as it has different sematics and use cases. + + { + // `bit_field` fields are accessed by using a dot: + Foo :: bit_field u16 { // backing type must be an integer or array of integers + x: i32 | 3, // signed integers will be signed extended on use + y: u16 | 2 + 3, // general expressions + z: My_Enum | SOME_CONSTANT, // ability to define the bit-width elsewhere + w: bool | 2 when SOME_CONSTANT > 10 else 1, + } + + v := Foo{} + v.x = 3 // truncates the value to fit into 3 bits + fmt.println(v.x) // accessing will convert `v.x` to an `i32` and do an appropriate sign extension + + + My_Enum :: enum u8 {A, B, C, D} + SOME_CONSTANT :: 7 + } + + { + // A `bit_field` is different from a struct in that you must specify the backing type. + // This backing type must be an integer or a fixed-length array of integers. + // This is useful if there needs to be a specific alignment or access pattern for the record. + + Bar :: bit_field u32 {} + Baz :: bit_field [4]u8 {} + } + + // IMPORTANT NOTES: + // * If _all_ of the fields in a bit_field are 1-bit in size and they are all booleans, + // please consider using a `bit_set` instead. + // * Odin's `bit_field` and C's bit-fields might not be compatible + // * Odin's `bit_field`s have a well defined layout (Least-Significant-Bit) + // * C's bit-fields on `struct`s are undefined and are not portable across targets and compilers + // * A `bit_field`'s field type can only be one of the following: + // * Integer + // * Boolean + // * Enum +} + main :: proc() { /* For More Odin Examples - https://github.com/odin-lang/examples @@ -2595,5 +2640,6 @@ main :: proc() { or_break_and_or_continue_operators() arbitrary_precision_mathematics() matrix_type() + bit_field_type() } } diff --git a/misc/featuregen/README.md b/misc/featuregen/README.md new file mode 100644 index 000000000..22a798cca --- /dev/null +++ b/misc/featuregen/README.md @@ -0,0 +1,28 @@ +# Featuregen + +This directory contains a python and CPP script that generates the needed information +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`: + +- `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 +- `target_microarch_counts`: an array of ints indexed by the architecture, each int represents the amount of microarchitectures available on that target, intended for easier iteration of the next global +- `microarch_features_list`: an array of a tuple like struct where the first string is a microarchitecture and the second is a comma-seperated list of all features that are enabled by default for it + +In order to get the default features for a microarchitecture there is a small CPP program that takes +a target triple and microarchitecture and spits out the default features, this is then parsed by the python script. + +This should be ran each time we update LLVM to stay in sync. + +If there are minor differences (like the Odin user using LLVM 14 and this table being generated on LLVM 17) it +does not impact much at all, the only thing it will do is make LLVM print a message that the feature is ignored (if it was added between 14 and 17 in this case). + +## 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. `python3 featuregen.py` +1. Copy the output into `src/build_settings.cpp` diff --git a/misc/featuregen/featuregen.cpp b/misc/featuregen/featuregen.cpp new file mode 100644 index 000000000..a1d00ab31 --- /dev/null +++ b/misc/featuregen/featuregen.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +// Dumps the default set of supported features for the given microarch. +int main(int argc, char **argv) { + if (argc < 3) { + llvm::errs() << "Error: first arg should be triple, second should be microarch\n"; + return 1; + } + + llvm::InitializeAllTargets(); + llvm::InitializeAllTargetMCs(); + + std::string error; + const llvm::Target* target = llvm::TargetRegistry::lookupTarget(argv[1], error); + + if (!target) { + llvm::errs() << "Error: " << error << "\n"; + return 1; + } + + auto STI = target->createMCSubtargetInfo(argv[1], argv[2], ""); + + std::string plus = "+"; + llvm::ArrayRef features = STI->getAllProcessorFeatures(); + for (const auto& feature : features) { + if (STI->checkFeatures(plus + feature.Key)) { + llvm::outs() << feature.Key << "\n"; + } + } + + return 0; +} diff --git a/misc/featuregen/featuregen.py b/misc/featuregen/featuregen.py new file mode 100644 index 000000000..da4cc68f5 --- /dev/null +++ b/misc/featuregen/featuregen.py @@ -0,0 +1,116 @@ +import subprocess +import tempfile +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", [], []), +]; + +SEEKING_CPUS = 0 +PARSING_CPUS = 1 +PARSING_FEATURES = 2 + +with tempfile.NamedTemporaryFile(suffix=".odin", delete=True) as temp_file: + temp_file.write(b"package main\n") + + for arch, target, triple, cpus, features in archs: + cmd = ["odin", "build", temp_file.name, "-file", "-build-mode:llvm", "-out:temp", "-target-features:\"help\"", f"-target:\"{target}\""] + process = subprocess.Popen(cmd, stderr=subprocess.PIPE, text=True) + + state = SEEKING_CPUS + for line in process.stderr: + + if state == SEEKING_CPUS: + if line == "Available CPUs for this target:\n": + state = PARSING_CPUS + + elif state == PARSING_CPUS: + if line == "Available features for this target:\n": + state = PARSING_FEATURES + continue + + parts = line.split(" -", maxsplit=1) + if len(parts) < 2: + continue + + cpu = parts[0].strip() + cpus.append(cpu) + + elif state == PARSING_FEATURES: + if line == "\n" and len(features) > 0: + break + + parts = line.split(" -", maxsplit=1) + if len(parts) < 2: + continue + + feature = parts[0].strip() + features.append(feature) + + process.wait() + if process.returncode != 0: + print(f"odin build returned with non-zero exit code {process.returncode}") + sys.exit(1) + + os.remove("temp.ll") + +def print_default_features(triple, microarch): + cmd = ["./featuregen", triple, microarch] + process = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True) + first = True + for line in process.stdout: + print("" if first else ",", line.strip(), sep="", end="") + first = False + process.wait() + if process.returncode != 0: + print(f"featuregen returned with non-zero exit code {process.returncode}") + sys.exit(1) + +print("// Generated with the featuregen script in `misc/featuregen`") +print("gb_global String target_microarch_list[TargetArch_COUNT] = {") +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)}"),') +print("};") + +print("") + +print("// Generated with the featuregen script in `misc/featuregen`") +print("gb_global String target_features_list[TargetArch_COUNT] = {") +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)}"),') +print("};") + +print("") + +print("// Generated with the featuregen script in `misc/featuregen`") +print("gb_global int target_microarch_counts[TargetArch_COUNT] = {") +print("\t// TargetArch_Invalid:") +print("\t0,") +for arch, target, triple, cpus, feature in archs: + print(f"\t// TargetArch_{arch}:") + print(f"\t{len(cpus)},") +print("};") + +print("") + +print("// Generated with the featuregen script in `misc/featuregen`") +print("gb_global MicroarchFeatureList microarch_features_list[] = {") +for arch, target, triple, cpus, features in archs: + print(f"\t// TargetArch_{arch}:") + for cpu in cpus: + print(f'\t{{ str_lit("{cpu}"), str_lit("', end="") + print_default_features(triple, cpu) + print('") },') +print("};") diff --git a/misc/roadmap.md b/misc/roadmap.md deleted file mode 100644 index 83fbbb695..000000000 --- a/misc/roadmap.md +++ /dev/null @@ -1,21 +0,0 @@ -# Odin Roadmap - -Not in any particular order - -* Custom backend to replace LLVM - - Improve SSA design to accommodate for lowering to a "bytecode" - - SSA optimizations - - COFF generation - - linker -* Type safe "macros" -* Documentation generator for "Entities" -* Multiple architecture support -* Inline assembly -* Linking options - - Executable - - Static/Dynamic Library -* Debug information - - pdb format too -* Command line tooling -* Compiler internals: - - Big numbers library diff --git a/src/bug_report.cpp b/src/bug_report.cpp index e919ba67b..dab8c4391 100644 --- a/src/bug_report.cpp +++ b/src/bug_report.cpp @@ -17,7 +17,7 @@ #include #endif -#if defined(GB_SYSTEM_OPENBSD) +#if defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_NETBSD) #include #include #endif @@ -204,14 +204,27 @@ gb_internal void report_cpu_info() { } #elif defined(GB_CPU_ARM) - /* - TODO(Jeroen): On *nix, perhaps query `/proc/cpuinfo`. - */ - #if defined(GB_ARCH_64_BIT) - gb_printf("ARM64\n"); - #else - gb_printf("ARM\n"); + bool generic = true; + + #if defined(GB_SYSTEM_OSX) + char cpu_name[128] = {}; + size_t cpu_name_size = 128; + if (sysctlbyname("machdep.cpu.brand_string", &cpu_name, &cpu_name_size, nullptr, 0) == 0) { + generic = false; + gb_printf("%s\n", (char *)&cpu_name[0]); + } #endif + + if (generic) { + /* + TODO(Jeroen): On *nix, perhaps query `/proc/cpuinfo`. + */ + #if defined(GB_ARCH_64_BIT) + gb_printf("ARM64\n"); + #else + gb_printf("ARM\n"); + #endif + } #else gb_printf("Unknown\n"); #endif @@ -238,7 +251,7 @@ gb_internal void report_ram_info() { int result = sysinfo(&info); if (result == 0x0) { - gb_printf("%lu MiB\n", info.totalram * info.mem_unit / gb_megabytes(1)); + gb_printf("%lu MiB\n", (unsigned long)(info.totalram * info.mem_unit / gb_megabytes(1))); } else { gb_printf("Unknown.\n"); } @@ -250,6 +263,14 @@ gb_internal void report_ram_info() { if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) { gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1)); } + #elif defined(GB_SYSTEM_NETBSD) + uint64_t ram_amount; + size_t val_size = sizeof(ram_amount); + + int mibs[] = { CTL_HW, HW_PHYSMEM64 }; + if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) { + gb_printf("%lu MiB\n", ram_amount / gb_megabytes(1)); + } #elif defined(GB_SYSTEM_OPENBSD) uint64_t ram_amount; size_t val_size = sizeof(ram_amount); @@ -889,6 +910,7 @@ gb_internal void report_os_info() { {"23D60", {23, 3, 0}, "macOS", {"Sonoma", {14, 3, 1}}}, {"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}}}, }; @@ -985,13 +1007,17 @@ gb_internal void report_os_info() { gb_printf("macOS Unknown (kernel: %d.%d.%d)\n", major, minor, patch); return; } - #elif defined(GB_SYSTEM_OPENBSD) + #elif defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_NETBSD) struct utsname un; if (uname(&un) != -1) { gb_printf("%s %s %s %s\n", un.sysname, un.release, un.version, un.machine); } else { - gb_printf("OpenBSD: Unknown\n"); + #if defined(GB_SYSTEM_NETBSD) + gb_printf("NetBSD: Unknown\n"); + #else + gb_printf("OpenBSD: Unknown\n"); + #endif } #elif defined(GB_SYSTEM_FREEBSD) #define freebsd_version_buffer 129 diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 03a95a19b..49bb83b22 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -18,10 +18,12 @@ enum TargetOsKind : u16 { TargetOs_essence, TargetOs_freebsd, TargetOs_openbsd, + TargetOs_netbsd, TargetOs_haiku, TargetOs_wasi, TargetOs_js, + TargetOs_orca, TargetOs_freestanding, @@ -71,6 +73,11 @@ enum Windows_Subsystem : u8 { Windows_Subsystem_COUNT, }; +struct MicroarchFeatureList { + String microarch; + String features; +}; + gb_global String target_os_names[TargetOs_COUNT] = { str_lit(""), str_lit("windows"), @@ -79,10 +86,12 @@ gb_global String target_os_names[TargetOs_COUNT] = { str_lit("essence"), str_lit("freebsd"), str_lit("openbsd"), + str_lit("netbsd"), str_lit("haiku"), str_lit("wasi"), str_lit("js"), + str_lit("orca"), str_lit("freestanding"), }; @@ -97,22 +106,7 @@ gb_global String target_arch_names[TargetArch_COUNT] = { str_lit("wasm64p32"), }; -gb_global String target_microarch_list[TargetArch_COUNT] = { - // TargetArch_Invalid, - str_lit("Invalid!"), - // TargetArch_amd64, - str_lit("alderlake,amdfam10,athlon-fx,athlon64,athlon64-sse3,atom_sse4_2,atom_sse4_2_movbe,barcelona,bdver1,bdver2,bdver3,bdver4,broadwell,btver1,btver2,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,generic,goldmont,goldmont-plus,goldmont_plus,grandridge,graniterapids,graniterapids-d,graniterapids_d,haswell,icelake-client,icelake-server,icelake_client,icelake_server,ivybridge,k8,k8-sse3,knl,knm,meteorlake,mic_avx512,native,nehalem,nocona,opteron,opteron-sse3,penryn,raptorlake,rocketlake,sandybridge,sapphirerapids,sierraforest,silvermont,skx,skylake,skylake-avx512,skylake_avx512,slm,tigerlake,tremont,westmere,x86-64,x86-64-v2,x86-64-v3,x86-64-v4,znver1,znver2,znver3,znver4"), - // TargetArch_i386, - str_lit("athlon,athlon-4,athlon-mp,athlon-tbird,athlon-xp,atom,bonnell,c3,c3-2,generic,geode,i386,i486,i586,i686,k6,k6-2,k6-3,lakemont,native,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,winchip-c6,winchip2,yonah"), - // 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,native,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,native,neoverse-512tvb,neoverse-e1,neoverse-n1,neoverse-n2,neoverse-v1,neoverse-v2,saphira,thunderx,thunderx2t99,thunderx3t110,thunderxt81,thunderxt83,thunderxt88,tsv110"), - // TargetArch_wasm32, - str_lit("generic"), - // TargetArch_wasm64p32, - str_lit("generic"), -}; +#include "build_settings_microarch.cpp" gb_global String target_endian_names[TargetEndian_COUNT] = { str_lit("little"), @@ -162,7 +156,6 @@ struct TargetMetrics { isize max_align; isize max_simd_align; String target_triplet; - String target_data_layout; TargetABIKind abi; }; @@ -194,6 +187,7 @@ struct QueryDataSetSettings { enum BuildModeKind { BuildMode_Executable, BuildMode_DynamicLibrary, + BuildMode_StaticLibrary, BuildMode_Object, BuildMode_Assembly, BuildMode_LLVM_IR, @@ -241,6 +235,12 @@ enum TimingsExportFormat : i32 { TimingsExportCSV = 2, }; +enum DependenciesExportFormat : i32 { + DependenciesExportUnspecified = 0, + DependenciesExportMake = 1, + DependenciesExportJson = 2, +}; + enum ErrorPosStyle { ErrorPosStyle_Default, // path(line:column) msg ErrorPosStyle_Unix, // path:line:column: msg @@ -280,10 +280,13 @@ enum VetFlags : u64 { VetFlag_Semicolon = 1u<<4, VetFlag_UnusedVariables = 1u<<5, VetFlag_UnusedImports = 1u<<6, + VetFlag_Deprecated = 1u<<7, + VetFlag_Cast = 1u<<8, + VetFlag_Tabs = 1u<<9, VetFlag_Unused = VetFlag_UnusedVariables|VetFlag_UnusedImports, - VetFlag_All = VetFlag_Unused|VetFlag_Shadowing|VetFlag_UsingStmt, + VetFlag_All = VetFlag_Unused|VetFlag_Shadowing|VetFlag_UsingStmt|VetFlag_Deprecated|VetFlag_Cast, VetFlag_Using = VetFlag_UsingStmt|VetFlag_UsingParam, }; @@ -305,6 +308,12 @@ u64 get_vet_flag_from_name(String const &name) { return VetFlag_Style; } else if (name == "semicolon") { return VetFlag_Semicolon; + } else if (name == "deprecated") { + return VetFlag_Deprecated; + } else if (name == "cast") { + return VetFlag_Cast; + } else if (name == "tabs") { + return VetFlag_Tabs; } return VetFlag_NONE; } @@ -317,7 +326,17 @@ enum SanitizerFlags : u32 { SanitizerFlag_Thread = 1u<<2, }; +struct BuildCacheData { + u64 crc; + String cache_dir; + // manifests + String files_path; + String args_path; + String env_path; + + bool copy_already_done; +}; // This stores the information for the specify architecture of this build struct BuildContext { @@ -375,9 +394,13 @@ struct BuildContext { bool show_timings; TimingsExportFormat export_timings_format; String export_timings_file; + DependenciesExportFormat export_dependencies_format; + String export_dependencies_file; bool show_unused; bool show_unused_with_location; bool show_more_timings; + bool show_defineables; + String export_defineables_file; bool show_system_calls; bool keep_temp_files; bool ignore_unknown_attributes; @@ -394,6 +417,8 @@ struct BuildContext { bool keep_object_files; bool disallow_do; + StringSet custom_attributes; + bool strict_style; bool ignore_warnings; @@ -405,11 +430,18 @@ struct BuildContext { bool ignore_lazy; bool ignore_llvm_build; + bool ignore_panic; bool ignore_microsoft_magic; bool linker_map_file; bool use_separate_modules; + bool module_per_file; + bool cached; + BuildCacheData build_cache_data; + + bool internal_no_inline; + bool no_threaded_checker; bool show_debug_messages; @@ -424,6 +456,8 @@ struct BuildContext { bool min_link_libs; + bool print_linker_flags; + RelocMode reloc_mode; bool disable_red_zone; @@ -435,7 +469,6 @@ struct BuildContext { u32 cmd_doc_flags; Array extra_packages; - StringSet test_names; bool test_all_packages; gbAffinity affinity; @@ -443,9 +476,9 @@ struct BuildContext { PtrMap defined_values; - BlockingMutex target_features_mutex; StringSet target_features_set; String target_features_string; + bool strict_target_features; String minimum_os_version_string; bool minimum_os_version_string_given; @@ -472,7 +505,18 @@ gb_internal isize MAX_ERROR_COLLECTOR_COUNT(void) { return build_context.max_error_count; } +#if defined(GB_SYSTEM_WINDOWS) + #include +#else + #include +#endif +// NOTE: AMD64 targets had their alignment on 128 bit ints bumped from 8 to 16 (undocumented of course). +#if LLVM_VERSION_MAJOR >= 18 + #define AMD64_MAX_ALIGNMENT 16 +#else + #define AMD64_MAX_ALIGNMENT 8 +#endif gb_global TargetMetrics target_windows_i386 = { TargetOs_windows, @@ -483,9 +527,8 @@ gb_global TargetMetrics target_windows_i386 = { gb_global TargetMetrics target_windows_amd64 = { TargetOs_windows, TargetArch_amd64, - 8, 8, 8, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 16, str_lit("x86_64-pc-windows-msvc"), - str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"), }; gb_global TargetMetrics target_linux_i386 = { @@ -498,32 +541,28 @@ gb_global TargetMetrics target_linux_i386 = { gb_global TargetMetrics target_linux_amd64 = { TargetOs_linux, TargetArch_amd64, - 8, 8, 8, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 16, str_lit("x86_64-pc-linux-gnu"), - str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"), }; gb_global TargetMetrics target_linux_arm64 = { TargetOs_linux, TargetArch_arm64, - 8, 8, 8, 16, + 8, 8, 16, 16, str_lit("aarch64-linux-elf"), - str_lit("e-m:o-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"), }; gb_global TargetMetrics target_linux_arm32 = { TargetOs_linux, TargetArch_arm32, 4, 4, 4, 8, - str_lit("arm-linux-gnu"), - str_lit("e-m:o-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"), + str_lit("arm-unknown-linux-gnueabihf"), }; gb_global TargetMetrics target_darwin_amd64 = { TargetOs_darwin, TargetArch_amd64, - 8, 8, 8, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 16, str_lit("x86_64-apple-macosx"), // NOTE: Changes during initialization based on build flags. - str_lit("e-m:o-i64:64-f80:128-n8:16:32:64-S128"), }; gb_global TargetMetrics target_darwin_arm64 = { @@ -531,7 +570,6 @@ gb_global TargetMetrics target_darwin_arm64 = { TargetArch_arm64, 8, 8, 16, 16, str_lit("arm64-apple-macosx"), // NOTE: Changes during initialization based on build flags. - str_lit("e-m:o-i64:64-i128:128-n32:64-S128"), }; gb_global TargetMetrics target_freebsd_i386 = { @@ -544,30 +582,49 @@ gb_global TargetMetrics target_freebsd_i386 = { gb_global TargetMetrics target_freebsd_amd64 = { TargetOs_freebsd, TargetArch_amd64, - 8, 8, 8, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 16, str_lit("x86_64-unknown-freebsd-elf"), - str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"), +}; + +gb_global TargetMetrics target_freebsd_arm64 = { + TargetOs_freebsd, + TargetArch_arm64, + 8, 8, 16, 16, + str_lit("aarch64-unknown-freebsd-elf"), }; gb_global TargetMetrics target_openbsd_amd64 = { TargetOs_openbsd, TargetArch_amd64, - 8, 8, 8, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 16, str_lit("x86_64-unknown-openbsd-elf"), - str_lit("e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"), +}; + +gb_global TargetMetrics target_netbsd_amd64 = { + TargetOs_netbsd, + TargetArch_amd64, + 8, 8, AMD64_MAX_ALIGNMENT, 16, + str_lit("x86_64-unknown-netbsd-elf"), +}; + +gb_global TargetMetrics target_netbsd_arm64 = { + TargetOs_netbsd, + TargetArch_arm64, + 8, 8, 16, 16, + str_lit("aarch64-unknown-netbsd-elf"), }; gb_global TargetMetrics target_haiku_amd64 = { TargetOs_haiku, TargetArch_amd64, - 8, 8, 8, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 16, str_lit("x86_64-unknown-haiku"), }; gb_global TargetMetrics target_essence_amd64 = { TargetOs_essence, TargetArch_amd64, - 8, 8, 8, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 16, str_lit("x86_64-pc-none-elf"), }; @@ -577,7 +634,6 @@ gb_global TargetMetrics target_freestanding_wasm32 = { TargetArch_wasm32, 4, 4, 8, 16, str_lit("wasm32-freestanding-js"), - str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"), }; gb_global TargetMetrics target_js_wasm32 = { @@ -585,7 +641,6 @@ gb_global TargetMetrics target_js_wasm32 = { TargetArch_wasm32, 4, 4, 8, 16, str_lit("wasm32-js-js"), - str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"), }; gb_global TargetMetrics target_wasi_wasm32 = { @@ -593,7 +648,14 @@ gb_global TargetMetrics target_wasi_wasm32 = { TargetArch_wasm32, 4, 4, 8, 16, str_lit("wasm32-wasi-js"), - str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"), +}; + + +gb_global TargetMetrics target_orca_wasm32 = { + TargetOs_orca, + TargetArch_wasm32, + 4, 4, 8, 16, + str_lit("wasm32-wasi-js"), }; @@ -602,7 +664,6 @@ gb_global TargetMetrics target_freestanding_wasm64p32 = { TargetArch_wasm64p32, 4, 8, 8, 16, str_lit("wasm32-freestanding-js"), - str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"), }; gb_global TargetMetrics target_js_wasm64p32 = { @@ -610,7 +671,6 @@ gb_global TargetMetrics target_js_wasm64p32 = { TargetArch_wasm64p32, 4, 8, 8, 16, str_lit("wasm32-js-js"), - str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"), }; gb_global TargetMetrics target_wasi_wasm64p32 = { @@ -618,7 +678,6 @@ gb_global TargetMetrics target_wasi_wasm64p32 = { TargetArch_wasm32, 4, 8, 8, 16, str_lit("wasm32-wasi-js"), - str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"), }; @@ -626,29 +685,34 @@ gb_global TargetMetrics target_wasi_wasm64p32 = { gb_global TargetMetrics target_freestanding_amd64_sysv = { TargetOs_freestanding, TargetArch_amd64, - 8, 8, 8, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 16, str_lit("x86_64-pc-none-gnu"), - str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"), TargetABI_SysV, }; gb_global TargetMetrics target_freestanding_amd64_win64 = { TargetOs_freestanding, TargetArch_amd64, - 8, 8, 8, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 16, str_lit("x86_64-pc-none-msvc"), - str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"), TargetABI_Win64, }; gb_global TargetMetrics target_freestanding_arm64 = { TargetOs_freestanding, TargetArch_arm64, - 8, 8, 8, 16, + 8, 8, 16, 16, str_lit("aarch64-none-elf"), - str_lit("e-m:o-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"), }; +gb_global TargetMetrics target_freestanding_arm32 = { + TargetOs_freestanding, + TargetArch_arm32, + 4, 4, 4, 8, + str_lit("arm-unknown-unknown-gnueabihf"), +}; + + struct NamedTargetMetrics { String name; TargetMetrics *metrics; @@ -670,6 +734,10 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freebsd_i386"), &target_freebsd_i386 }, { str_lit("freebsd_amd64"), &target_freebsd_amd64 }, + { str_lit("freebsd_arm64"), &target_freebsd_arm64 }, + + { str_lit("netbsd_amd64"), &target_netbsd_amd64 }, + { str_lit("netbsd_arm64"), &target_netbsd_arm64 }, { str_lit("openbsd_amd64"), &target_openbsd_amd64 }, { str_lit("haiku_amd64"), &target_haiku_amd64 }, @@ -677,6 +745,7 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freestanding_wasm32"), &target_freestanding_wasm32 }, { str_lit("wasi_wasm32"), &target_wasi_wasm32 }, { str_lit("js_wasm32"), &target_js_wasm32 }, + { str_lit("orca_wasm32"), &target_orca_wasm32 }, { str_lit("freestanding_wasm64p32"), &target_freestanding_wasm64p32 }, { str_lit("js_wasm64p32"), &target_js_wasm64p32 }, @@ -686,6 +755,7 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freestanding_amd64_win64"), &target_freestanding_amd64_win64 }, { str_lit("freestanding_arm64"), &target_freestanding_arm64 }, + { str_lit("freestanding_arm32"), &target_freestanding_arm32 }, }; gb_global NamedTargetMetrics *selected_target_metrics; @@ -808,15 +878,6 @@ gb_internal bool is_arch_x86(void) { return false; } -gb_internal bool allow_check_foreign_filepath(void) { - switch (build_context.metrics.arch) { - case TargetArch_wasm32: - case TargetArch_wasm64p32: - return false; - } - return true; -} - // TODO(bill): OS dependent versions for the BuildContext // join_path // is_dir @@ -1378,6 +1439,8 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta bc->thread_count = gb_max(bc->affinity.thread_count, 1); } + string_set_init(&bc->custom_attributes); + bc->ODIN_VENDOR = str_lit("odin"); bc->ODIN_VERSION = ODIN_VERSION; bc->ODIN_ROOT = odin_root_dir(); @@ -1424,9 +1487,19 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta metrics = &target_darwin_amd64; #endif #elif defined(GB_SYSTEM_FREEBSD) - metrics = &target_freebsd_amd64; + #if defined(GB_CPU_ARM) + metrics = &target_freebsd_arm64; + #else + metrics = &target_freebsd_amd64; + #endif #elif defined(GB_SYSTEM_OPENBSD) metrics = &target_openbsd_amd64; + #elif defined(GB_SYSTEM_NETBSD) + #if defined(GB_CPU_ARM) + metrics = &target_netbsd_arm64; + #else + metrics = &target_netbsd_amd64; + #endif #elif defined(GB_SYSTEM_HAIKU) metrics = &target_haiku_amd64; #elif defined(GB_CPU_ARM) @@ -1434,6 +1507,16 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta #else metrics = &target_linux_amd64; #endif + #elif defined(GB_CPU_ARM) + #if defined(GB_SYSTEM_WINDOWS) + #error "Build Error: Unsupported architecture" + #elif defined(GB_SYSTEM_OSX) + #error "Build Error: Unsupported architecture" + #elif defined(GB_SYSTEM_FREEBSD) + #error "Build Error: Unsupported architecture" + #else + metrics = &target_linux_arm32; + #endif #else #if defined(GB_SYSTEM_WINDOWS) metrics = &target_windows_i386; @@ -1465,26 +1548,6 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta } bc->metrics = *metrics; - if (metrics->os == TargetOs_darwin) { - if (!bc->minimum_os_version_string_given) { - bc->minimum_os_version_string = str_lit("11.0.0"); - } - - switch (subtarget) { - case Subtarget_Default: - bc->metrics.target_triplet = concatenate_strings(permanent_allocator(), bc->metrics.target_triplet, bc->minimum_os_version_string); - break; - case Subtarget_iOS: - if (metrics->arch == TargetArch_arm64) { - bc->metrics.target_triplet = str_lit("arm64-apple-ios"); - } else if (metrics->arch == TargetArch_amd64) { - bc->metrics.target_triplet = str_lit("x86_64-apple-ios"); - } else { - GB_PANIC("Unknown architecture for darwin"); - } - break; - } - } bc->ODIN_OS = target_os_names[metrics->os]; bc->ODIN_ARCH = target_arch_names[metrics->arch]; @@ -1520,94 +1583,94 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta bc->ODIN_WINDOWS_SUBSYSTEM = windows_subsystem_names[Windows_Subsystem_CONSOLE]; } - // NOTE(zangent): The linker flags to set the build architecture are different - // across OSs. It doesn't make sense to allocate extra data on the heap - // here, so I just #defined the linker flags to keep things concise. - if (bc->metrics.arch == TargetArch_amd64) { - switch (bc->metrics.os) { - case TargetOs_windows: - bc->link_flags = str_lit("/machine:x64 "); + if (metrics->os == TargetOs_darwin && subtarget == Subtarget_iOS) { + switch (metrics->arch) { + case TargetArch_arm64: + bc->metrics.target_triplet = str_lit("arm64-apple-ios"); break; - case TargetOs_darwin: - bc->link_flags = str_lit("-arch x86_64 "); - break; - case TargetOs_linux: - bc->link_flags = str_lit("-arch x86-64 "); - break; - case TargetOs_freebsd: - bc->link_flags = str_lit("-arch x86-64 "); - break; - case TargetOs_openbsd: - bc->link_flags = str_lit("-arch x86-64 "); - break; - case TargetOs_haiku: - bc->link_flags = str_lit("-arch x86-64 "); - break; - } - } else if (bc->metrics.arch == TargetArch_i386) { - switch (bc->metrics.os) { - case TargetOs_windows: - bc->link_flags = str_lit("/machine:x86 "); - break; - case TargetOs_darwin: - gb_printf_err("Unsupported architecture\n"); - gb_exit(1); - break; - case TargetOs_linux: - bc->link_flags = str_lit("-arch x86 "); - break; - case TargetOs_freebsd: - bc->link_flags = str_lit("-arch x86 "); - break; - } - } else if (bc->metrics.arch == TargetArch_arm32) { - switch (bc->metrics.os) { - case TargetOs_linux: - bc->link_flags = str_lit("-arch arm "); + case TargetArch_amd64: + bc->metrics.target_triplet = str_lit("x86_64-apple-ios"); break; default: - gb_printf_err("Compiler Error: Unsupported architecture\n"); - gb_exit(1); + GB_PANIC("Unknown architecture for darwin"); } - } else if (bc->metrics.arch == TargetArch_arm64) { - switch (bc->metrics.os) { - case TargetOs_darwin: - bc->link_flags = str_lit("-arch arm64 "); + } + + if (bc->metrics.os == TargetOs_windows) { + switch (bc->metrics.arch) { + case TargetArch_amd64: + bc->link_flags = str_lit("/machine:x64 "); break; - case TargetOs_linux: - bc->link_flags = str_lit("-arch aarch64 "); + case TargetArch_i386: + bc->link_flags = str_lit("/machine:x86 "); break; } + } else if (bc->metrics.os == TargetOs_darwin) { + bc->link_flags = concatenate3_strings(permanent_allocator(), + str_lit("-target "), bc->metrics.target_triplet, str_lit(" ")); } else if (is_arch_wasm()) { gbString link_flags = gb_string_make(heap_allocator(), " "); // link_flags = gb_string_appendc(link_flags, "--export-all "); // link_flags = gb_string_appendc(link_flags, "--export-table "); - link_flags = gb_string_appendc(link_flags, "--allow-undefined "); // if (bc->metrics.arch == TargetArch_wasm64) { // link_flags = gb_string_appendc(link_flags, "-mwasm64 "); // } - if (bc->no_entry_point) { + if (bc->metrics.os != TargetOs_orca) { + link_flags = gb_string_appendc(link_flags, "--allow-undefined "); + } + if (bc->no_entry_point || bc->metrics.os == TargetOs_orca) { link_flags = gb_string_appendc(link_flags, "--no-entry "); } - + bc->link_flags = make_string_c(link_flags); - + // Disallow on wasm bc->use_separate_modules = false; } else { - gb_printf_err("Compiler Error: Unsupported architecture\n"); - gb_exit(1); + // 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 + // out what the actual target for linking is, + // for example, on x86/alpine/musl it HAS to be `x86_64-alpine-linux-musl` to link correctly. + // + // Note that codegen will still target the triplet we specify, but the intricate details of + // a target shouldn't matter as much to codegen (if it does at all) as it does to linking. } - if (bc->ODIN_DEBUG && !bc->custom_optimization_level) { + // NOTE: needs to be done after adding the -target flag to the linker flags so the linker + // does not annoy the user with version warnings. + if (metrics->os == TargetOs_darwin) { + if (!bc->minimum_os_version_string_given) { + bc->minimum_os_version_string = str_lit("11.0.0"); + } + + if (subtarget == Subtarget_Default) { + bc->metrics.target_triplet = concatenate_strings(permanent_allocator(), bc->metrics.target_triplet, bc->minimum_os_version_string); + } + } + + if (!bc->custom_optimization_level) { // NOTE(bill): when building with `-debug` but not specifying an optimization level // default to `-o:none` to improve the debug symbol generation by default - bc->optimization_level = -1; // -o:none + if (bc->ODIN_DEBUG) { + bc->optimization_level = -1; // -o:none + } else { + bc->optimization_level = 0; // -o:minimal + } } bc->optimization_level = gb_clamp(bc->optimization_level, -1, 3); - if (bc->metrics.os != TargetOs_windows) { +#if defined(GB_SYSTEM_WINDOWS) + if (bc->optimization_level <= 0) { + if (!is_arch_wasm()) { + bc->use_separate_modules = true; + } + } +#endif + + + // TODO: Static map calls are bugged on `amd64sysv` abi. + if (bc->metrics.os != TargetOs_windows && bc->metrics.arch == TargetArch_amd64) { // ENFORCE DYNAMIC MAP CALLS bc->dynamic_map_calls = true; } @@ -1623,12 +1686,6 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta if (bc->metrics.os == TargetOs_freestanding) { bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR = !bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR; - } else if (is_arch_wasm()) { - if (bc->metrics.os == TargetOs_js || bc->metrics.os == TargetOs_wasi) { - // TODO(bill): Should these even have a default "heap-like" allocator? - } - bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR = true; - bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR = !bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR; } } @@ -1638,48 +1695,30 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta #include "microsoft_craziness.h" #endif +// NOTE: the target feature and microarch lists are all sorted, so if it turns out to be slow (I don't think it will) +// a binary search is possible. -gb_internal Array split_by_comma(String const &list) { - isize n = 1; - for (isize i = 0; i < list.len; i++) { - if (list.text[i] == ',') { - n++; +gb_internal bool check_single_target_feature_is_valid(String const &feature_list, String const &feature) { + String_Iterator it = {feature_list, 0}; + for (;;) { + String str = string_split_iterator(&it, ','); + if (str == "") break; + if (str == feature) { + return true; } } - auto res = array_make(heap_allocator(), n); - String s = list; - for (isize i = 0; i < n; i++) { - isize m = string_index_byte(s, ','); - if (m < 0) { - res[i] = s; - break; - } - res[i] = substring(s, 0, m); - s = substring(s, m+1, s.len); - } - return res; + return false; } -gb_internal bool check_target_feature_is_valid(TokenPos pos, String const &feature) { - // TODO(bill): check_target_feature_is_valid - return true; -} - -gb_internal bool check_target_feature_is_enabled(TokenPos pos, String const &target_feature_list) { - BuildContext *bc = &build_context; - mutex_lock(&bc->target_features_mutex); - defer (mutex_unlock(&bc->target_features_mutex)); - - auto items = split_by_comma(target_feature_list); - array_free(&items); - for (String const &item : items) { - if (!check_target_feature_is_valid(pos, item)) { - error(pos, "Target feature '%.*s' is not valid", LIT(item)); - return false; - } - if (!string_set_exists(&bc->target_features_set, item)) { - error(pos, "Target feature '%.*s' is not enabled", LIT(item)); +gb_internal bool check_target_feature_is_valid(String const &feature, TargetArchKind arch, String *invalid) { + String feature_list = target_features_list[arch]; + String_Iterator it = {feature, 0}; + for (;;) { + String str = string_split_iterator(&it, ','); + if (str == "") break; + if (!check_single_target_feature_is_valid(feature_list, str)) { + if (invalid) *invalid = str; return false; } } @@ -1687,54 +1726,58 @@ gb_internal bool check_target_feature_is_enabled(TokenPos pos, String const &tar return true; } -gb_internal void enable_target_feature(TokenPos pos, String const &target_feature_list) { - BuildContext *bc = &build_context; - mutex_lock(&bc->target_features_mutex); - defer (mutex_unlock(&bc->target_features_mutex)); +gb_internal bool check_target_feature_is_valid_globally(String const &feature, String *invalid) { + String_Iterator it = {feature, 0}; + for (;;) { + String str = string_split_iterator(&it, ','); + if (str == "") break; - auto items = split_by_comma(target_feature_list); - for (String const &item : items) { - if (!check_target_feature_is_valid(pos, item)) { - error(pos, "Target feature '%.*s' is not valid", LIT(item)); - continue; + bool valid = false; + for (int arch = TargetArch_Invalid; arch < TargetArch_COUNT; arch += 1) { + if (check_target_feature_is_valid(str, cast(TargetArchKind)arch, invalid)) { + valid = true; + break; + } } - string_set_add(&bc->target_features_set, item); + if (!valid) { + if (invalid) *invalid = str; + return false; + } } - array_free(&items); + + return true; } +gb_internal bool check_target_feature_is_valid_for_target_arch(String const &feature, String *invalid) { + return check_target_feature_is_valid(feature, build_context.metrics.arch, invalid); +} -gb_internal char const *target_features_set_to_cstring(gbAllocator allocator, bool with_quotes, bool with_plus) { - isize len = 0; - isize i = 0; - for (String const &feature : build_context.target_features_set) { - if (i != 0) { - len += 1; +gb_internal bool check_target_feature_is_enabled(String const &feature, String *not_enabled) { + String_Iterator it = {feature, 0}; + for (;;) { + String str = string_split_iterator(&it, ','); + if (str == "") break; + if (!string_set_exists(&build_context.target_features_set, str)) { + if (not_enabled) *not_enabled = str; + return false; } - len += feature.len; - if (with_quotes) len += 2; - if (with_plus) len += 1; - i += 1; } - char *features = gb_alloc_array(allocator, char, len+1); - len = 0; - i = 0; - for (String const &feature : build_context.target_features_set) { - if (i != 0) { - features[len++] = ','; + + return true; +} + +gb_internal bool check_target_feature_is_superset_of(String const &superset, String const &of, String *missing) { + String_Iterator it = {of, 0}; + for (;;) { + String str = string_split_iterator(&it, ','); + if (str == "") break; + if (!check_single_target_feature_is_valid(superset, str)) { + if (missing) *missing = str; + return false; } - - if (with_quotes) features[len++] = '"'; - if (with_plus) features[len++] = '+'; - gb_memmove(features + len, feature.text, feature.len); - len += feature.len; - if (with_quotes) features[len++] = '"'; - i += 1; } - features[len++] = 0; - - return features; + return true; } // NOTE(Jeroen): Set/create the output and other paths and report an error as appropriate. @@ -1777,10 +1820,11 @@ gb_internal bool init_build_paths(String init_filename) { #if defined(GB_SYSTEM_WINDOWS) if (bc->metrics.os == TargetOs_windows) { if (bc->resource_filepath.len > 0) { - bc->build_paths[BuildPath_RC] = path_from_string(ha, bc->resource_filepath); - bc->build_paths[BuildPath_RES] = path_from_string(ha, bc->resource_filepath); - bc->build_paths[BuildPath_RC].ext = copy_string(ha, STR_LIT("rc")); - bc->build_paths[BuildPath_RES].ext = copy_string(ha, STR_LIT("res")); + bc->build_paths[BuildPath_RES] = path_from_string(ha, bc->resource_filepath); + if (!string_ends_with(bc->resource_filepath, str_lit(".res"))) { + bc->build_paths[BuildPath_RC] = path_from_string(ha, bc->resource_filepath); + bc->build_paths[BuildPath_RC].ext = copy_string(ha, STR_LIT("rc")); + } } if (bc->pdb_filepath.len > 0) { @@ -1868,7 +1912,12 @@ gb_internal bool init_build_paths(String init_filename) { } else if (build_context.metrics.os == TargetOs_darwin) { output_extension = STR_LIT("dylib"); } - } else if (build_context.build_mode == BuildMode_Object) { + } else if (build_context.build_mode == BuildMode_StaticLibrary) { + output_extension = STR_LIT("a"); + if (build_context.metrics.os == TargetOs_windows) { + output_extension = STR_LIT("lib"); + } + }else if (build_context.build_mode == BuildMode_Object) { // By default use a .o object extension. output_extension = STR_LIT("o"); @@ -2019,16 +2068,13 @@ gb_internal bool init_build_paths(String init_filename) { case TargetOs_essence: case TargetOs_freebsd: case TargetOs_openbsd: + case TargetOs_netbsd: case TargetOs_haiku: gb_printf_err("-no-crt on unix systems requires either -default-to-nil-allocator or -default-to-panic-allocator to also be present because the default allocator requires crt\n"); return false; } } - if (bc->target_features_string.len != 0) { - enable_target_feature({}, bc->target_features_string); - } - return true; } diff --git a/src/build_settings_microarch.cpp b/src/build_settings_microarch.cpp new file mode 100644 index 000000000..02b507031 --- /dev/null +++ b/src/build_settings_microarch.cpp @@ -0,0 +1,462 @@ +// Generated with the featuregen script in `misc/featuregen` +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"), + // 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"), + // 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"), + // 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"), + // TargetArch_wasm32: + str_lit("bleeding-edge,generic,mvp"), + // TargetArch_wasm64p32: + str_lit("bleeding-edge,generic,mvp"), +}; + +// Generated with the featuregen script in `misc/featuregen` +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"), + // 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"), + // 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"), + // 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"), + // 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"), + // 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"), +}; + +// Generated with the featuregen script in `misc/featuregen` +gb_global int target_microarch_counts[TargetArch_COUNT] = { + // TargetArch_Invalid: + 0, + // TargetArch_amd64: + 120, + // TargetArch_i386: + 120, + // TargetArch_arm32: + 90, + // TargetArch_arm64: + 63, + // TargetArch_wasm32: + 3, + // TargetArch_wasm64p32: + 3, +}; + +// Generated with the featuregen script in `misc/featuregen` +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("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") }, + { str_lit("athlon-mp"), str_lit("3dnow,3dnowa,64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("athlon-tbird"), str_lit("3dnow,3dnowa,64bit-mode,cmov,cx8,mmx,nopl,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("athlon-xp"), str_lit("3dnow,3dnowa,64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("athlon64"), 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("athlon64-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("atom"), str_lit("64bit,64bit-mode,cmov,cx16,cx8,fxsr,idivl-to-divb,idivq-to-divl,lea-sp,lea-uses-ag,mmx,movbe,no-bypass-delay,nopl,pad-short-functions,sahf,slow-two-mem-ops,slow-unaligned-mem-16,sse,sse2,sse3,ssse3,vzeroupper,x87") }, + { str_lit("atom_sse4_2"), str_lit("64bit,64bit-mode,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-7bytenop,fast-movbe,fxsr,idivq-to-divl,mmx,movbe,no-bypass-delay,nopl,pclmul,popcnt,prfchw,rdrnd,sahf,slow-incdec,slow-lea,slow-pmulld,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,use-slm-arith-costs,vzeroupper,x87") }, + { str_lit("atom_sse4_2_movbe"), str_lit("64bit,64bit-mode,aes,clflushopt,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-7bytenop,fast-movbe,fsgsbase,fxsr,idivq-to-divl,mmx,movbe,no-bypass-delay,nopl,pclmul,popcnt,prfchw,rdrnd,rdseed,sahf,sha,slow-incdec,slow-lea,slow-pmulld,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,use-slm-arith-costs,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("barcelona"), 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("bdver1"), str_lit("64bit,64bit-mode,aes,avx,branchfusion,cmov,crc32,cx16,cx8,fast-11bytenop,fast-scalar-shift-masks,fma4,fxsr,lwp,lzcnt,mmx,nopl,pclmul,popcnt,prfchw,sahf,sbb-dep-breaking,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,vzeroupper,x87,xop,xsave") }, + { str_lit("bdver2"), str_lit("64bit,64bit-mode,aes,avx,bmi,branchfusion,cmov,crc32,cx16,cx8,f16c,fast-11bytenop,fast-bextr,fast-movbe,fast-scalar-shift-masks,fma,fma4,fxsr,lwp,lzcnt,mmx,nopl,pclmul,popcnt,prfchw,sahf,sbb-dep-breaking,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,tbm,vzeroupper,x87,xop,xsave") }, + { str_lit("bdver3"), str_lit("64bit,64bit-mode,aes,avx,bmi,branchfusion,cmov,crc32,cx16,cx8,f16c,fast-11bytenop,fast-bextr,fast-movbe,fast-scalar-shift-masks,fma,fma4,fsgsbase,fxsr,lwp,lzcnt,mmx,nopl,pclmul,popcnt,prfchw,sahf,sbb-dep-breaking,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,tbm,vzeroupper,x87,xop,xsave,xsaveopt") }, + { str_lit("bdver4"), str_lit("64bit,64bit-mode,aes,avx,avx2,bmi,bmi2,branchfusion,cmov,crc32,cx16,cx8,f16c,fast-11bytenop,fast-bextr,fast-movbe,fast-scalar-shift-masks,fma,fma4,fsgsbase,fxsr,lwp,lzcnt,mmx,movbe,mwaitx,nopl,pclmul,popcnt,prfchw,rdrnd,sahf,sbb-dep-breaking,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,tbm,vzeroupper,x87,xop,xsave,xsaveopt") }, + { str_lit("bonnell"), str_lit("64bit,64bit-mode,cmov,cx16,cx8,fxsr,idivl-to-divb,idivq-to-divl,lea-sp,lea-uses-ag,mmx,movbe,no-bypass-delay,nopl,pad-short-functions,sahf,slow-two-mem-ops,slow-unaligned-mem-16,sse,sse2,sse3,ssse3,vzeroupper,x87") }, + { str_lit("broadwell"), str_lit("64bit,64bit-mode,adx,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,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { str_lit("btver1"), str_lit("64bit,64bit-mode,cmov,cx16,cx8,fast-15bytenop,fast-scalar-shift-masks,fast-vector-shift-masks,fxsr,lzcnt,mmx,nopl,popcnt,prfchw,sahf,sbb-dep-breaking,slow-shld,sse,sse2,sse3,sse4a,ssse3,vzeroupper,x87") }, + { str_lit("btver2"), str_lit("64bit,64bit-mode,aes,avx,bmi,cmov,crc32,cx16,cx8,f16c,fast-15bytenop,fast-bextr,fast-hops,fast-lzcnt,fast-movbe,fast-scalar-shift-masks,fast-vector-shift-masks,fxsr,lzcnt,mmx,movbe,nopl,pclmul,popcnt,prfchw,sahf,sbb-dep-breaking,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,x87,xsave,xsaveopt") }, + { str_lit("c3"), str_lit("3dnow,64bit-mode,mmx,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { 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("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") }, + { str_lit("core2"), str_lit("64bit,64bit-mode,cmov,cx16,cx8,fxsr,macrofusion,mmx,nopl,sahf,slow-unaligned-mem-16,sse,sse2,sse3,ssse3,vzeroupper,x87") }, + { str_lit("core_2_duo_sse4_1"), 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("core_2_duo_ssse3"), str_lit("64bit,64bit-mode,cmov,cx16,cx8,fxsr,macrofusion,mmx,nopl,sahf,slow-unaligned-mem-16,sse,sse2,sse3,ssse3,vzeroupper,x87") }, + { str_lit("core_2nd_gen_avx"), str_lit("64bit,64bit-mode,avx,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fxsr,idivq-to-divl,macrofusion,mmx,no-bypass-delay-mov,nopl,pclmul,popcnt,sahf,slow-3ops-lea,slow-unaligned-mem-32,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { str_lit("core_3rd_gen_avx"), 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_4th_gen_avx"), 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") }, + { str_lit("core_4th_gen_avx_tsx"), 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") }, + { str_lit("core_5th_gen_avx"), str_lit("64bit,64bit-mode,adx,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,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { str_lit("core_5th_gen_avx_tsx"), str_lit("64bit,64bit-mode,adx,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,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { str_lit("core_aes_pclmulqdq"), str_lit("64bit,64bit-mode,cmov,crc32,cx16,cx8,fxsr,macrofusion,mmx,no-bypass-delay-mov,nopl,pclmul,popcnt,sahf,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87") }, + { str_lit("core_i7_sse4_2"), 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("corei7"), 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("corei7-avx"), str_lit("64bit,64bit-mode,avx,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fxsr,idivq-to-divl,macrofusion,mmx,no-bypass-delay-mov,nopl,pclmul,popcnt,sahf,slow-3ops-lea,slow-unaligned-mem-32,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { str_lit("emeraldrapids"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,amx-bf16,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,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("generic"), str_lit("64bit,64bit-mode,cx8,fast-15bytenop,fast-scalar-fsqrt,idivq-to-divl,macrofusion,slow-3ops-lea,sse,sse2,vzeroupper,x87") }, + { str_lit("geode"), str_lit("3dnow,3dnowa,64bit-mode,cx8,mmx,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { 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("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") }, + { 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") }, + { str_lit("haswell"), 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") }, + { str_lit("i386"), str_lit("64bit-mode,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("i486"), str_lit("64bit-mode,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("i586"), str_lit("64bit-mode,cx8,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("i686"), str_lit("64bit-mode,cmov,cx8,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("icelake-client"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,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,fsrm,fxsr,gfni,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,rdpid,rdrnd,rdseed,sahf,sha,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("icelake-server"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,bmi,bmi2,clflushopt,clwb,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,fsrm,fxsr,gfni,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-256-bit,prfchw,rdpid,rdrnd,rdseed,sahf,sha,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("icelake_client"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,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,fsrm,fxsr,gfni,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,rdpid,rdrnd,rdseed,sahf,sha,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("icelake_server"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,bmi,bmi2,clflushopt,clwb,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,fsrm,fxsr,gfni,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-256-bit,prfchw,rdpid,rdrnd,rdseed,sahf,sha,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("ivybridge"), 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("k6"), str_lit("64bit-mode,cx8,mmx,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("k6-2"), str_lit("3dnow,64bit-mode,cx8,mmx,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("k6-3"), str_lit("3dnow,64bit-mode,cx8,mmx,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("k8"), 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("k8-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("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("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("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") }, + { str_lit("pentium-mmx"), str_lit("64bit-mode,cx8,mmx,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium2"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium3"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium3m"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium4"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium4m"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium_4"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium_4_sse3"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,sse3,vzeroupper,x87") }, + { str_lit("pentium_ii"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium_iii"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium_iii_no_xmm_regs"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,nopl,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") }, + { str_lit("pentium_mmx"), str_lit("64bit-mode,cx8,mmx,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium_pro"), str_lit("64bit-mode,cmov,cx8,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentiumpro"), str_lit("64bit-mode,cmov,cx8,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("prescott"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,sse3,vzeroupper,x87") }, + { str_lit("raptorlake"), 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("rocketlake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,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,fsrm,fxsr,gfni,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,rdpid,rdrnd,rdseed,sahf,sha,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("sandybridge"), str_lit("64bit,64bit-mode,avx,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fxsr,idivq-to-divl,macrofusion,mmx,no-bypass-delay-mov,nopl,pclmul,popcnt,sahf,slow-3ops-lea,slow-unaligned-mem-32,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { str_lit("sapphirerapids"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,amx-bf16,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,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("sierraforest"), 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("silvermont"), str_lit("64bit,64bit-mode,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-7bytenop,fast-movbe,fxsr,idivq-to-divl,mmx,movbe,no-bypass-delay,nopl,pclmul,popcnt,prfchw,rdrnd,sahf,slow-incdec,slow-lea,slow-pmulld,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,use-slm-arith-costs,vzeroupper,x87") }, + { str_lit("skx"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avx512bw,avx512cd,avx512dq,avx512f,avx512vl,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("skylake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,bmi,bmi2,clflushopt,cmov,crc32,cx16,cx8,ermsb,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,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,popcnt,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("skylake-avx512"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avx512bw,avx512cd,avx512dq,avx512f,avx512vl,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("skylake_avx512"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avx512bw,avx512cd,avx512dq,avx512f,avx512vl,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("slm"), str_lit("64bit,64bit-mode,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-7bytenop,fast-movbe,fxsr,idivq-to-divl,mmx,movbe,no-bypass-delay,nopl,pclmul,popcnt,prfchw,rdrnd,sahf,slow-incdec,slow-lea,slow-pmulld,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,use-slm-arith-costs,vzeroupper,x87") }, + { str_lit("tigerlake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vp2intersect,avx512vpopcntdq,bmi,bmi2,clflushopt,clwb,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,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,pku,popcnt,prefer-256-bit,prfchw,rdpid,rdrnd,rdseed,sahf,sha,shstk,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("tremont"), str_lit("64bit,64bit-mode,aes,clflushopt,clwb,cmov,crc32,cx16,cx8,fast-movbe,fsgsbase,fxsr,gfni,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("westmere"), str_lit("64bit,64bit-mode,cmov,crc32,cx16,cx8,fxsr,macrofusion,mmx,no-bypass-delay-mov,nopl,pclmul,popcnt,sahf,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87") }, + { str_lit("winchip-c6"), str_lit("64bit-mode,mmx,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("winchip2"), str_lit("3dnow,64bit-mode,mmx,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("x86-64"), str_lit("64bit,64bit-mode,cmov,cx8,fxsr,idivq-to-divl,macrofusion,mmx,nopl,slow-3ops-lea,slow-incdec,sse,sse2,vzeroupper,x87") }, + { str_lit("x86-64-v2"), str_lit("64bit,64bit-mode,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fxsr,idivq-to-divl,macrofusion,mmx,nopl,popcnt,sahf,slow-3ops-lea,slow-unaligned-mem-32,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87") }, + { str_lit("x86-64-v3"), str_lit("64bit,64bit-mode,allow-light-256-bit,avx,avx2,bmi,bmi2,cmov,crc32,cx16,cx8,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,fxsr,idivq-to-divl,lzcnt,macrofusion,mmx,movbe,nopl,popcnt,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave") }, + { str_lit("x86-64-v4"), str_lit("64bit,64bit-mode,allow-light-256-bit,avx,avx2,avx512bw,avx512cd,avx512dq,avx512f,avx512vl,bmi,bmi2,cmov,crc32,cx16,cx8,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,fma,fxsr,idivq-to-divl,lzcnt,macrofusion,mmx,movbe,nopl,popcnt,prefer-256-bit,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave") }, + { str_lit("yonah"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,sse3,vzeroupper,x87") }, + { str_lit("znver1"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,bmi,bmi2,branchfusion,clflushopt,clzero,cmov,crc32,cx16,cx8,f16c,fast-15bytenop,fast-bextr,fast-lzcnt,fast-movbe,fast-scalar-fsqrt,fast-scalar-shift-masks,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,lzcnt,mmx,movbe,mwaitx,nopl,pclmul,popcnt,prfchw,rdrnd,rdseed,sahf,sbb-dep-breaking,sha,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("znver2"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,bmi,bmi2,branchfusion,clflushopt,clwb,clzero,cmov,crc32,cx16,cx8,f16c,fast-15bytenop,fast-bextr,fast-lzcnt,fast-movbe,fast-scalar-fsqrt,fast-scalar-shift-masks,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,lzcnt,mmx,movbe,mwaitx,nopl,pclmul,popcnt,prfchw,rdpid,rdpru,rdrnd,rdseed,sahf,sbb-dep-breaking,sha,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,vzeroupper,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("znver3"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,bmi,bmi2,branchfusion,clflushopt,clwb,clzero,cmov,crc32,cx16,cx8,f16c,fast-15bytenop,fast-bextr,fast-lzcnt,fast-movbe,fast-scalar-fsqrt,fast-scalar-shift-masks,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fsrm,fxsr,invpcid,lzcnt,macrofusion,mmx,movbe,mwaitx,nopl,pclmul,pku,popcnt,prfchw,rdpid,rdpru,rdrnd,rdseed,sahf,sbb-dep-breaking,sha,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,vaes,vpclmulqdq,vzeroupper,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("znver4"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,bmi,bmi2,branchfusion,clflushopt,clwb,clzero,cmov,crc32,cx16,cx8,evex512,f16c,fast-15bytenop,fast-bextr,fast-lzcnt,fast-movbe,fast-scalar-fsqrt,fast-scalar-shift-masks,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fsrm,fxsr,gfni,invpcid,lzcnt,macrofusion,mmx,movbe,mwaitx,nopl,pclmul,pku,popcnt,prfchw,rdpid,rdpru,rdrnd,rdseed,sahf,sbb-dep-breaking,sha,shstk,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,vaes,vpclmulqdq,vzeroupper,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, + // 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("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") }, + { str_lit("athlon-mp"), str_lit("32bit-mode,3dnow,3dnowa,cmov,cx8,fxsr,mmx,nopl,slow-shld,slow-unaligned-mem-16,sse,vzeroupper,x87") }, + { str_lit("athlon-tbird"), str_lit("32bit-mode,3dnow,3dnowa,cmov,cx8,mmx,nopl,slow-shld,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("athlon-xp"), str_lit("32bit-mode,3dnow,3dnowa,cmov,cx8,fxsr,mmx,nopl,slow-shld,slow-unaligned-mem-16,sse,vzeroupper,x87") }, + { str_lit("athlon64"), 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("athlon64-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("atom"), str_lit("32bit-mode,64bit,cmov,cx16,cx8,fxsr,idivl-to-divb,idivq-to-divl,lea-sp,lea-uses-ag,mmx,movbe,no-bypass-delay,nopl,pad-short-functions,sahf,slow-two-mem-ops,slow-unaligned-mem-16,sse,sse2,sse3,ssse3,vzeroupper,x87") }, + { str_lit("atom_sse4_2"), str_lit("32bit-mode,64bit,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-7bytenop,fast-movbe,fxsr,idivq-to-divl,mmx,movbe,no-bypass-delay,nopl,pclmul,popcnt,prfchw,rdrnd,sahf,slow-incdec,slow-lea,slow-pmulld,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,use-slm-arith-costs,vzeroupper,x87") }, + { str_lit("atom_sse4_2_movbe"), str_lit("32bit-mode,64bit,aes,clflushopt,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-7bytenop,fast-movbe,fsgsbase,fxsr,idivq-to-divl,mmx,movbe,no-bypass-delay,nopl,pclmul,popcnt,prfchw,rdrnd,rdseed,sahf,sha,slow-incdec,slow-lea,slow-pmulld,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,use-slm-arith-costs,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("barcelona"), 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("bdver1"), str_lit("32bit-mode,64bit,aes,avx,branchfusion,cmov,crc32,cx16,cx8,fast-11bytenop,fast-scalar-shift-masks,fma4,fxsr,lwp,lzcnt,mmx,nopl,pclmul,popcnt,prfchw,sahf,sbb-dep-breaking,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,vzeroupper,x87,xop,xsave") }, + { str_lit("bdver2"), str_lit("32bit-mode,64bit,aes,avx,bmi,branchfusion,cmov,crc32,cx16,cx8,f16c,fast-11bytenop,fast-bextr,fast-movbe,fast-scalar-shift-masks,fma,fma4,fxsr,lwp,lzcnt,mmx,nopl,pclmul,popcnt,prfchw,sahf,sbb-dep-breaking,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,tbm,vzeroupper,x87,xop,xsave") }, + { str_lit("bdver3"), str_lit("32bit-mode,64bit,aes,avx,bmi,branchfusion,cmov,crc32,cx16,cx8,f16c,fast-11bytenop,fast-bextr,fast-movbe,fast-scalar-shift-masks,fma,fma4,fsgsbase,fxsr,lwp,lzcnt,mmx,nopl,pclmul,popcnt,prfchw,sahf,sbb-dep-breaking,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,tbm,vzeroupper,x87,xop,xsave,xsaveopt") }, + { str_lit("bdver4"), str_lit("32bit-mode,64bit,aes,avx,avx2,bmi,bmi2,branchfusion,cmov,crc32,cx16,cx8,f16c,fast-11bytenop,fast-bextr,fast-movbe,fast-scalar-shift-masks,fma,fma4,fsgsbase,fxsr,lwp,lzcnt,mmx,movbe,mwaitx,nopl,pclmul,popcnt,prfchw,rdrnd,sahf,sbb-dep-breaking,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,tbm,vzeroupper,x87,xop,xsave,xsaveopt") }, + { str_lit("bonnell"), str_lit("32bit-mode,64bit,cmov,cx16,cx8,fxsr,idivl-to-divb,idivq-to-divl,lea-sp,lea-uses-ag,mmx,movbe,no-bypass-delay,nopl,pad-short-functions,sahf,slow-two-mem-ops,slow-unaligned-mem-16,sse,sse2,sse3,ssse3,vzeroupper,x87") }, + { str_lit("broadwell"), str_lit("32bit-mode,64bit,adx,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,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { str_lit("btver1"), str_lit("32bit-mode,64bit,cmov,cx16,cx8,fast-15bytenop,fast-scalar-shift-masks,fast-vector-shift-masks,fxsr,lzcnt,mmx,nopl,popcnt,prfchw,sahf,sbb-dep-breaking,slow-shld,sse,sse2,sse3,sse4a,ssse3,vzeroupper,x87") }, + { str_lit("btver2"), str_lit("32bit-mode,64bit,aes,avx,bmi,cmov,crc32,cx16,cx8,f16c,fast-15bytenop,fast-bextr,fast-hops,fast-lzcnt,fast-movbe,fast-scalar-shift-masks,fast-vector-shift-masks,fxsr,lzcnt,mmx,movbe,nopl,pclmul,popcnt,prfchw,sahf,sbb-dep-breaking,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,x87,xsave,xsaveopt") }, + { str_lit("c3"), str_lit("32bit-mode,3dnow,mmx,slow-unaligned-mem-16,vzeroupper,x87") }, + { 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("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") }, + { str_lit("core2"), str_lit("32bit-mode,64bit,cmov,cx16,cx8,fxsr,macrofusion,mmx,nopl,sahf,slow-unaligned-mem-16,sse,sse2,sse3,ssse3,vzeroupper,x87") }, + { str_lit("core_2_duo_sse4_1"), 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("core_2_duo_ssse3"), str_lit("32bit-mode,64bit,cmov,cx16,cx8,fxsr,macrofusion,mmx,nopl,sahf,slow-unaligned-mem-16,sse,sse2,sse3,ssse3,vzeroupper,x87") }, + { str_lit("core_2nd_gen_avx"), str_lit("32bit-mode,64bit,avx,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fxsr,idivq-to-divl,macrofusion,mmx,no-bypass-delay-mov,nopl,pclmul,popcnt,sahf,slow-3ops-lea,slow-unaligned-mem-32,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { str_lit("core_3rd_gen_avx"), 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_4th_gen_avx"), 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") }, + { str_lit("core_4th_gen_avx_tsx"), 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") }, + { str_lit("core_5th_gen_avx"), str_lit("32bit-mode,64bit,adx,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,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { str_lit("core_5th_gen_avx_tsx"), str_lit("32bit-mode,64bit,adx,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,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { str_lit("core_aes_pclmulqdq"), str_lit("32bit-mode,64bit,cmov,crc32,cx16,cx8,fxsr,macrofusion,mmx,no-bypass-delay-mov,nopl,pclmul,popcnt,sahf,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87") }, + { str_lit("core_i7_sse4_2"), 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("corei7"), 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("corei7-avx"), str_lit("32bit-mode,64bit,avx,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fxsr,idivq-to-divl,macrofusion,mmx,no-bypass-delay-mov,nopl,pclmul,popcnt,sahf,slow-3ops-lea,slow-unaligned-mem-32,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { str_lit("emeraldrapids"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,amx-bf16,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,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("generic"), str_lit("32bit-mode,64bit,cx8,fast-15bytenop,fast-scalar-fsqrt,idivq-to-divl,macrofusion,slow-3ops-lea,vzeroupper,x87") }, + { str_lit("geode"), str_lit("32bit-mode,3dnow,3dnowa,cx8,mmx,slow-unaligned-mem-16,vzeroupper,x87") }, + { 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("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") }, + { 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") }, + { str_lit("haswell"), 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") }, + { str_lit("i386"), str_lit("32bit-mode,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("i486"), str_lit("32bit-mode,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("i586"), str_lit("32bit-mode,cx8,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("i686"), str_lit("32bit-mode,cmov,cx8,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("icelake-client"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,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,fsrm,fxsr,gfni,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,rdpid,rdrnd,rdseed,sahf,sha,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("icelake-server"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,bmi,bmi2,clflushopt,clwb,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,fsrm,fxsr,gfni,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-256-bit,prfchw,rdpid,rdrnd,rdseed,sahf,sha,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("icelake_client"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,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,fsrm,fxsr,gfni,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,rdpid,rdrnd,rdseed,sahf,sha,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("icelake_server"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,bmi,bmi2,clflushopt,clwb,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,fsrm,fxsr,gfni,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-256-bit,prfchw,rdpid,rdrnd,rdseed,sahf,sha,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("ivybridge"), 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("k6"), str_lit("32bit-mode,cx8,mmx,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("k6-2"), str_lit("32bit-mode,3dnow,cx8,mmx,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("k6-3"), str_lit("32bit-mode,3dnow,cx8,mmx,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("k8"), 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("k8-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("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("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("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") }, + { str_lit("pentium-mmx"), str_lit("32bit-mode,cx8,mmx,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("pentium2"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("pentium3"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,vzeroupper,x87") }, + { str_lit("pentium3m"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,vzeroupper,x87") }, + { str_lit("pentium4"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium4m"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium_4"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium_4_sse3"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,sse3,vzeroupper,x87") }, + { str_lit("pentium_ii"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("pentium_iii"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,vzeroupper,x87") }, + { str_lit("pentium_iii_no_xmm_regs"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,vzeroupper,x87") }, + { str_lit("pentium_m"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium_mmx"), str_lit("32bit-mode,cx8,mmx,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("pentium_pro"), str_lit("32bit-mode,cmov,cx8,nopl,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("pentiumpro"), str_lit("32bit-mode,cmov,cx8,nopl,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("prescott"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,sse3,vzeroupper,x87") }, + { str_lit("raptorlake"), 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("rocketlake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,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,fsrm,fxsr,gfni,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,rdpid,rdrnd,rdseed,sahf,sha,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("sandybridge"), str_lit("32bit-mode,64bit,avx,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fxsr,idivq-to-divl,macrofusion,mmx,no-bypass-delay-mov,nopl,pclmul,popcnt,sahf,slow-3ops-lea,slow-unaligned-mem-32,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { str_lit("sapphirerapids"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,amx-bf16,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,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("sierraforest"), 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("silvermont"), str_lit("32bit-mode,64bit,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-7bytenop,fast-movbe,fxsr,idivq-to-divl,mmx,movbe,no-bypass-delay,nopl,pclmul,popcnt,prfchw,rdrnd,sahf,slow-incdec,slow-lea,slow-pmulld,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,use-slm-arith-costs,vzeroupper,x87") }, + { str_lit("skx"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avx512bw,avx512cd,avx512dq,avx512f,avx512vl,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("skylake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,bmi,bmi2,clflushopt,cmov,crc32,cx16,cx8,ermsb,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,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,popcnt,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("skylake-avx512"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avx512bw,avx512cd,avx512dq,avx512f,avx512vl,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("skylake_avx512"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avx512bw,avx512cd,avx512dq,avx512f,avx512vl,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("slm"), str_lit("32bit-mode,64bit,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-7bytenop,fast-movbe,fxsr,idivq-to-divl,mmx,movbe,no-bypass-delay,nopl,pclmul,popcnt,prfchw,rdrnd,sahf,slow-incdec,slow-lea,slow-pmulld,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,use-slm-arith-costs,vzeroupper,x87") }, + { str_lit("tigerlake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vp2intersect,avx512vpopcntdq,bmi,bmi2,clflushopt,clwb,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,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,pku,popcnt,prefer-256-bit,prfchw,rdpid,rdrnd,rdseed,sahf,sha,shstk,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("tremont"), str_lit("32bit-mode,64bit,aes,clflushopt,clwb,cmov,crc32,cx16,cx8,fast-movbe,fsgsbase,fxsr,gfni,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("westmere"), str_lit("32bit-mode,64bit,cmov,crc32,cx16,cx8,fxsr,macrofusion,mmx,no-bypass-delay-mov,nopl,pclmul,popcnt,sahf,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87") }, + { str_lit("winchip-c6"), str_lit("32bit-mode,mmx,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("winchip2"), str_lit("32bit-mode,3dnow,mmx,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("x86-64"), str_lit("32bit-mode,64bit,cmov,cx8,fxsr,idivq-to-divl,macrofusion,mmx,nopl,slow-3ops-lea,slow-incdec,sse,sse2,vzeroupper,x87") }, + { str_lit("x86-64-v2"), str_lit("32bit-mode,64bit,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fxsr,idivq-to-divl,macrofusion,mmx,nopl,popcnt,sahf,slow-3ops-lea,slow-unaligned-mem-32,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87") }, + { str_lit("x86-64-v3"), str_lit("32bit-mode,64bit,allow-light-256-bit,avx,avx2,bmi,bmi2,cmov,crc32,cx16,cx8,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,fxsr,idivq-to-divl,lzcnt,macrofusion,mmx,movbe,nopl,popcnt,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave") }, + { str_lit("x86-64-v4"), str_lit("32bit-mode,64bit,allow-light-256-bit,avx,avx2,avx512bw,avx512cd,avx512dq,avx512f,avx512vl,bmi,bmi2,cmov,crc32,cx16,cx8,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,fma,fxsr,idivq-to-divl,lzcnt,macrofusion,mmx,movbe,nopl,popcnt,prefer-256-bit,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave") }, + { str_lit("yonah"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,sse3,vzeroupper,x87") }, + { str_lit("znver1"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,bmi,bmi2,branchfusion,clflushopt,clzero,cmov,crc32,cx16,cx8,f16c,fast-15bytenop,fast-bextr,fast-lzcnt,fast-movbe,fast-scalar-fsqrt,fast-scalar-shift-masks,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,lzcnt,mmx,movbe,mwaitx,nopl,pclmul,popcnt,prfchw,rdrnd,rdseed,sahf,sbb-dep-breaking,sha,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("znver2"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,bmi,bmi2,branchfusion,clflushopt,clwb,clzero,cmov,crc32,cx16,cx8,f16c,fast-15bytenop,fast-bextr,fast-lzcnt,fast-movbe,fast-scalar-fsqrt,fast-scalar-shift-masks,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,lzcnt,mmx,movbe,mwaitx,nopl,pclmul,popcnt,prfchw,rdpid,rdpru,rdrnd,rdseed,sahf,sbb-dep-breaking,sha,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,vzeroupper,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("znver3"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,bmi,bmi2,branchfusion,clflushopt,clwb,clzero,cmov,crc32,cx16,cx8,f16c,fast-15bytenop,fast-bextr,fast-lzcnt,fast-movbe,fast-scalar-fsqrt,fast-scalar-shift-masks,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fsrm,fxsr,invpcid,lzcnt,macrofusion,mmx,movbe,mwaitx,nopl,pclmul,pku,popcnt,prfchw,rdpid,rdpru,rdrnd,rdseed,sahf,sbb-dep-breaking,sha,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,vaes,vpclmulqdq,vzeroupper,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("znver4"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,bmi,bmi2,branchfusion,clflushopt,clwb,clzero,cmov,crc32,cx16,cx8,evex512,f16c,fast-15bytenop,fast-bextr,fast-lzcnt,fast-movbe,fast-scalar-fsqrt,fast-scalar-shift-masks,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fsrm,fxsr,gfni,invpcid,lzcnt,macrofusion,mmx,movbe,mwaitx,nopl,pclmul,pku,popcnt,prfchw,rdpid,rdpru,rdrnd,rdseed,sahf,sbb-dep-breaking,sha,shstk,slow-shld,sse,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,vaes,vpclmulqdq,vzeroupper,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, + // TargetArch_arm32: + { str_lit("arm1020e"), str_lit("armv5te,v4t,v5t,v5te") }, + { str_lit("arm1020t"), str_lit("armv5t,v4t,v5t") }, + { str_lit("arm1022e"), str_lit("armv5te,v4t,v5t,v5te") }, + { str_lit("arm10e"), str_lit("armv5te,v4t,v5t,v5te") }, + { str_lit("arm10tdmi"), str_lit("armv5t,v4t,v5t") }, + { str_lit("arm1136j-s"), str_lit("armv6,dsp,v4t,v5t,v5te,v6") }, + { str_lit("arm1136jf-s"), str_lit("armv6,dsp,fp64,fpregs,fpregs64,slowfpvmlx,v4t,v5t,v5te,v6,vfp2,vfp2sp") }, + { str_lit("arm1156t2-s"), str_lit("armv6t2,dsp,thumb2,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v8m") }, + { str_lit("arm1156t2f-s"), str_lit("armv6t2,dsp,fp64,fpregs,fpregs64,slowfpvmlx,thumb2,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v8m,vfp2,vfp2sp") }, + { str_lit("arm1176jz-s"), str_lit("armv6kz,trustzone,v4t,v5t,v5te,v6,v6k") }, + { str_lit("arm1176jzf-s"), str_lit("armv6kz,fp64,fpregs,fpregs64,slowfpvmlx,trustzone,v4t,v5t,v5te,v6,v6k,vfp2,vfp2sp") }, + { str_lit("arm710t"), str_lit("armv4t,v4t") }, + { str_lit("arm720t"), str_lit("armv4t,v4t") }, + { str_lit("arm7tdmi"), str_lit("armv4t,v4t") }, + { str_lit("arm7tdmi-s"), str_lit("armv4t,v4t") }, + { str_lit("arm8"), str_lit("armv4") }, + { str_lit("arm810"), str_lit("armv4") }, + { str_lit("arm9"), str_lit("armv4t,v4t") }, + { str_lit("arm920"), str_lit("armv4t,v4t") }, + { str_lit("arm920t"), str_lit("armv4t,v4t") }, + { str_lit("arm922t"), str_lit("armv4t,v4t") }, + { str_lit("arm926ej-s"), str_lit("armv5te,v4t,v5t,v5te") }, + { str_lit("arm940t"), str_lit("armv4t,v4t") }, + { str_lit("arm946e-s"), str_lit("armv5te,v4t,v5t,v5te") }, + { str_lit("arm966e-s"), str_lit("armv5te,v4t,v5t,v5te") }, + { 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-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-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-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") }, + { str_lit("cortex-a75"), str_lit("a75,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-a76"), str_lit("a76,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-a76ae"), str_lit("a76,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-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-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") }, + { str_lit("cortex-m23"), str_lit("8msecext,acquire-release,armv8-m.base,db,hwdiv,mclass,no-branch-predictor,no-movt,noarm,strict-align,thumb-mode,v4t,v5t,v5te,v6,v6m,v7clrex,v8m") }, + { str_lit("cortex-m3"), str_lit("armv7-m,db,hwdiv,loop-align,m3,mclass,no-branch-predictor,noarm,thumb-mode,thumb2,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m") }, + { 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-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") }, + { str_lit("cortex-r4"), str_lit("armv7-r,avoid-partial-cpsr,db,dsp,hwdiv,perfmon,r4,rclass,ret-addr-stack,thumb2,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m") }, + { str_lit("cortex-r4f"), str_lit("armv7-r,avoid-partial-cpsr,db,dsp,fp64,fpregs,fpregs64,hwdiv,perfmon,r4,rclass,ret-addr-stack,slow-fp-brcc,slowfpvfmx,slowfpvmlx,thumb2,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3d16,vfp3d16sp") }, + { str_lit("cortex-r5"), str_lit("armv7-r,avoid-partial-cpsr,db,dsp,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,perfmon,r5,rclass,ret-addr-stack,slow-fp-brcc,slowfpvfmx,slowfpvmlx,thumb2,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3d16,vfp3d16sp") }, + { str_lit("cortex-r52"), str_lit("acquire-release,armv8-r,crc,d32,db,dfb,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpao,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,perfmon,r52,rclass,thumb2,use-misched,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-r7"), str_lit("armv7-r,avoid-partial-cpsr,db,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,perfmon,r7,rclass,ret-addr-stack,slow-fp-brcc,slowfpvfmx,slowfpvmlx,thumb2,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3d16,vfp3d16sp") }, + { str_lit("cortex-r8"), str_lit("armv7-r,avoid-partial-cpsr,db,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,perfmon,rclass,ret-addr-stack,slow-fp-brcc,slowfpvfmx,slowfpvmlx,thumb2,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3d16,vfp3d16sp") }, + { str_lit("cortex-x1"), str_lit("aclass,acquire-release,aes,armv8.2-a,cortex-x1,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-x1c"), str_lit("aclass,acquire-release,aes,armv8.2-a,cortex-x1c,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("cyclone"), str_lit("aclass,acquire-release,aes,armv8-a,avoid-movs-shop,avoid-partial-cpsr,crc,crypto,d32,db,disable-postra-scheduler,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,neonfp,perfmon,ret-addr-stack,sha2,slowfpvfmx,slowfpvmlx,swift,thumb2,trustzone,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,zcz") }, + { str_lit("ep9312"), str_lit("armv4t,v4t") }, + { str_lit("exynos-m3"), str_lit("aclass,acquire-release,aes,armv8-a,crc,crypto,d32,db,dont-widen-vmovs,dsp,expand-fp-mlx,exynos,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs64,fuse-aes,fuse-literals,hwdiv,hwdiv-arm,mp,neon,perfmon,prof-unpr,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,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,wide-stride-vfp,zcz") }, + { str_lit("exynos-m4"), 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("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("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") }, + { str_lit("neoverse-n1"), str_lit("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("neoverse-n2"), str_lit("aclass,acquire-release,armv9-a,bf16,crc,d32,db,dotprod,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs64,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("neoverse-v1"), str_lit("aclass,acquire-release,aes,armv8.4-a,bf16,crc,crypto,d32,db,dotprod,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs16,fpregs64,fullfp16,hwdiv,hwdiv-arm,i8mm,mp,neon,perfmon,ras,sha2,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8.1a,v8.2a,v8.3a,v8.4a,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, + { str_lit("sc000"), str_lit("armv6-m,db,mclass,no-branch-predictor,noarm,strict-align,thumb-mode,v4t,v5t,v5te,v6,v6m") }, + { str_lit("sc300"), str_lit("armv7-m,db,hwdiv,m3,mclass,no-branch-predictor,noarm,thumb-mode,thumb2,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m") }, + { str_lit("strongarm"), str_lit("armv4") }, + { 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("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("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") }, + { str_lit("apple-a13"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,am,apple-a13,arith-bcc-fusion,arith-cbz-fusion,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,dit,dotprod,el2vmsa,el3,flagm,fp-armv8,fp16fml,fullfp16,fuse-aes,fuse-crypto-eor,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,ras,rcpc,rcpc-immo,rdm,sel2,sha2,sha3,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-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-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-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-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") }, + { str_lit("cortex-a65"), str_lit("CONTEXTIDREL2,a65,aes,ccpp,crc,crypto,dotprod,el2vmsa,el3,enable-select-opt,fp-armv8,fullfp16,fuse-address,fuse-adrp-add,fuse-aes,fuse-literals,lor,lse,neon,pan,pan-rwv,perfmon,predictable-select-expensive,ras,rcpc,rdm,sha2,ssbs,uaops,v8.1a,v8.2a,v8a,vh") }, + { str_lit("cortex-a65ae"), str_lit("CONTEXTIDREL2,a65,aes,ccpp,crc,crypto,dotprod,el2vmsa,el3,enable-select-opt,fp-armv8,fullfp16,fuse-address,fuse-adrp-add,fuse-aes,fuse-literals,lor,lse,neon,pan,pan-rwv,perfmon,predictable-select-expensive,ras,rcpc,rdm,sha2,ssbs,uaops,v8.1a,v8.2a,v8a,vh") }, + { 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-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") }, + { str_lit("cortex-a76ae"), 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") }, + { str_lit("cortex-a77"), str_lit("CONTEXTIDREL2,a77,addr-lsl-fast,aes,alu-lsl-fast,ccpp,cmp-bcc-fusion,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") }, + { str_lit("cortex-a78"), str_lit("CONTEXTIDREL2,a78,addr-lsl-fast,aes,alu-lsl-fast,ccpp,cmp-bcc-fusion,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,spe,ssbs,uaops,use-postra-scheduler,v8.1a,v8.2a,v8a,vh") }, + { str_lit("cortex-a78c"), str_lit("CONTEXTIDREL2,a78c,addr-lsl-fast,aes,alu-lsl-fast,ccpp,cmp-bcc-fusion,crc,crypto,dotprod,el2vmsa,el3,enable-select-opt,flagm,fp-armv8,fullfp16,fuse-adrp-add,fuse-aes,lor,lse,neon,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,ras,rcpc,rdm,sha2,spe,ssbs,uaops,use-postra-scheduler,v8.1a,v8.2a,v8a,vh") }, + { str_lit("cortex-r82"), str_lit("CONTEXTIDREL2,ccidx,ccpp,complxnum,cortex-r82,crc,dit,dotprod,flagm,fp-armv8,fp16fml,fullfp16,jsconv,lse,neon,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,sb,sel2,specrestrict,ssbs,tlb-rmi,tracev8.4,uaops,use-postra-scheduler,v8r") }, + { str_lit("cortex-x1"), str_lit("CONTEXTIDREL2,addr-lsl-fast,aes,alu-lsl-fast,ccpp,cmp-bcc-fusion,cortex-x1,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,spe,ssbs,uaops,use-postra-scheduler,v8.1a,v8.2a,v8a,vh") }, + { 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("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") }, + { str_lit("exynos-m5"), 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") }, + { str_lit("falkor"), str_lit("addr-lsl-fast,aes,alu-lsl-fast,crc,crypto,el2vmsa,el3,falkor,fp-armv8,neon,perfmon,predictable-select-expensive,rdm,sha2,slow-strqro-store,store-pair-suppress,use-postra-scheduler,v8a,zcz,zcz-gp") }, + { str_lit("generic"), str_lit("enable-select-opt,ete,fp-armv8,fuse-adrp-add,fuse-aes,neon,trbe,use-postra-scheduler") }, + { str_lit("kryo"), str_lit("addr-lsl-fast,aes,alu-lsl-fast,crc,crypto,el2vmsa,el3,fp-armv8,kryo,neon,perfmon,predictable-select-expensive,sha2,store-pair-suppress,use-postra-scheduler,v8a,zcz,zcz-gp") }, + { str_lit("neoverse-512tvb"), str_lit("CONTEXTIDREL2,addr-lsl-fast,aes,alu-lsl-fast,am,bf16,ccdp,ccidx,ccpp,complxnum,crc,crypto,dit,dotprod,el2vmsa,el3,enable-select-opt,flagm,fp-armv8,fp16fml,fullfp16,fuse-adrp-add,fuse-aes,i8mm,jsconv,lor,lse,lse2,mpam,neon,neoverse512tvb,nv,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,rand,ras,rcpc,rcpc-immo,rdm,sel2,sha2,spe,ssbs,sve,tlb-rmi,tracev8.4,uaops,use-postra-scheduler,v8.1a,v8.2a,v8.3a,v8.4a,v8a,vh") }, + { str_lit("neoverse-e1"), str_lit("CONTEXTIDREL2,aes,ccpp,crc,crypto,dotprod,el2vmsa,el3,fp-armv8,fullfp16,fuse-adrp-add,fuse-aes,lor,lse,neon,neoversee1,pan,pan-rwv,perfmon,ras,rcpc,rdm,sha2,ssbs,uaops,use-postra-scheduler,v8.1a,v8.2a,v8a,vh") }, + { str_lit("neoverse-n1"), str_lit("CONTEXTIDREL2,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,neoversen1,pan,pan-rwv,perfmon,predictable-select-expensive,ras,rcpc,rdm,sha2,spe,ssbs,uaops,use-postra-scheduler,v8.1a,v8.2a,v8a,vh") }, + { str_lit("neoverse-n2"), str_lit("CONTEXTIDREL2,addr-lsl-fast,altnzcv,alu-lsl-fast,am,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,dit,dotprod,el2vmsa,el3,enable-select-opt,ete,flagm,fp-armv8,fptoint,fullfp16,fuse-adrp-add,fuse-aes,i8mm,jsconv,lor,lse,lse2,mec,mpam,mte,neon,neoversen2,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("neoverse-v1"), str_lit("CONTEXTIDREL2,addr-lsl-fast,aes,alu-lsl-fast,am,bf16,ccdp,ccidx,ccpp,complxnum,crc,crypto,dit,dotprod,el2vmsa,el3,enable-select-opt,flagm,fp-armv8,fp16fml,fullfp16,fuse-adrp-add,fuse-aes,i8mm,jsconv,lor,lse,lse2,mpam,neon,neoversev1,no-sve-fp-ld1r,nv,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,rand,ras,rcpc,rcpc-immo,rdm,sel2,sha2,spe,ssbs,sve,tlb-rmi,tracev8.4,uaops,use-postra-scheduler,v8.1a,v8.2a,v8.3a,v8.4a,v8a,vh") }, + { str_lit("neoverse-v2"), str_lit("CONTEXTIDREL2,addr-lsl-fast,altnzcv,alu-lsl-fast,am,bf16,bti,ccdp,ccidx,ccpp,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,neoversev2,nv,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,predres,rand,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("saphira"), str_lit("CONTEXTIDREL2,addr-lsl-fast,aes,alu-lsl-fast,am,ccidx,ccpp,complxnum,crc,crypto,dit,dotprod,el2vmsa,el3,flagm,fp-armv8,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,ras,rcpc,rcpc-immo,rdm,saphira,sel2,sha2,spe,store-pair-suppress,tlb-rmi,tracev8.4,uaops,use-postra-scheduler,v8.1a,v8.2a,v8.3a,v8.4a,v8a,vh,zcz,zcz-gp") }, + { str_lit("thunderx"), str_lit("aes,crc,crypto,el2vmsa,el3,fp-armv8,neon,perfmon,predictable-select-expensive,sha2,store-pair-suppress,thunderx,use-postra-scheduler,v8a") }, + { str_lit("thunderx2t99"), str_lit("CONTEXTIDREL2,aes,aggressive-fma,arith-bcc-fusion,crc,crypto,el2vmsa,el3,fp-armv8,lor,lse,neon,pan,predictable-select-expensive,rdm,sha2,store-pair-suppress,thunderx2t99,use-postra-scheduler,v8.1a,v8a,vh") }, + { str_lit("thunderx3t110"), str_lit("CONTEXTIDREL2,aes,aggressive-fma,arith-bcc-fusion,balance-fp-ops,ccidx,ccpp,complxnum,crc,crypto,el2vmsa,el3,fp-armv8,jsconv,lor,lse,neon,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,ras,rcpc,rdm,sha2,store-pair-suppress,strict-align,thunderx3t110,uaops,use-postra-scheduler,v8.1a,v8.2a,v8.3a,v8a,vh") }, + { str_lit("thunderxt81"), str_lit("aes,crc,crypto,el2vmsa,el3,fp-armv8,neon,perfmon,predictable-select-expensive,sha2,store-pair-suppress,thunderxt81,use-postra-scheduler,v8a") }, + { str_lit("thunderxt83"), str_lit("aes,crc,crypto,el2vmsa,el3,fp-armv8,neon,perfmon,predictable-select-expensive,sha2,store-pair-suppress,thunderxt83,use-postra-scheduler,v8a") }, + { str_lit("thunderxt88"), str_lit("aes,crc,crypto,el2vmsa,el3,fp-armv8,neon,perfmon,predictable-select-expensive,sha2,store-pair-suppress,thunderxt88,use-postra-scheduler,v8a") }, + { str_lit("tsv110"), str_lit("CONTEXTIDREL2,aes,ccpp,complxnum,crc,crypto,dotprod,el2vmsa,el3,fp-armv8,fp16fml,fullfp16,fuse-aes,jsconv,lor,lse,neon,pan,pan-rwv,perfmon,ras,rdm,sha2,spe,store-pair-suppress,tsv110,uaops,use-postra-scheduler,v8.1a,v8.2a,v8a,vh") }, + // TargetArch_wasm32: + { 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("") }, + // TargetArch_wasm64p32: + { 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 diff --git a/src/cached.cpp b/src/cached.cpp new file mode 100644 index 000000000..4ad65ee9e --- /dev/null +++ b/src/cached.cpp @@ -0,0 +1,461 @@ +gb_internal GB_COMPARE_PROC(string_cmp) { + String const &x = *(String *)a; + String const &y = *(String *)b; + return string_compare(x, y); +} + +gb_internal bool recursively_delete_directory(wchar_t *wpath_c) { +#if defined(GB_SYSTEM_WINDOWS) + auto const is_dots_w = [](wchar_t const *str) -> bool { + if (!str) { + return false; + } + return wcscmp(str, L".") == 0 || wcscmp(str, L"..") == 0; + }; + + TEMPORARY_ALLOCATOR_GUARD(); + + wchar_t dir_path[MAX_PATH] = {}; + wchar_t filename[MAX_PATH] = {}; + wcscpy_s(dir_path, wpath_c); + wcscat_s(dir_path, L"\\*"); + + wcscpy_s(filename, wpath_c); + wcscat_s(filename, L"\\"); + + + WIN32_FIND_DATAW find_file_data = {}; + HANDLE hfind = FindFirstFileW(dir_path, &find_file_data); + if (hfind == INVALID_HANDLE_VALUE) { + return false; + } + defer (FindClose(hfind)); + + wcscpy_s(dir_path, filename); + + for (;;) { + if (FindNextFileW(hfind, &find_file_data)) { + if (is_dots_w(find_file_data.cFileName)) { + continue; + } + wcscat_s(filename, find_file_data.cFileName); + + if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (!recursively_delete_directory(filename)) { + return false; + } + RemoveDirectoryW(filename); + wcscpy_s(filename, dir_path); + } else { + if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { + _wchmod(filename, _S_IWRITE); + } + if (!DeleteFileW(filename)) { + return false; + } + wcscpy_s(filename, dir_path); + } + } else { + if (GetLastError() == ERROR_NO_MORE_FILES) { + break; + } + return false; + } + } + + + return RemoveDirectoryW(wpath_c); +#else + return false; +#endif +} + +gb_internal bool recursively_delete_directory(String const &path) { +#if defined(GB_SYSTEM_WINDOWS) + String16 wpath = string_to_string16(permanent_allocator(), path); + wchar_t *wpath_c = alloc_wstring(permanent_allocator(), wpath); + return recursively_delete_directory(wpath_c); +#else + return false; +#endif +} + +gb_internal bool try_clear_cache(void) { + return recursively_delete_directory(str_lit(".odin-cache")); +} + + +gb_internal u64 crc64_with_seed(void const *data, isize len, u64 seed) { + isize remaining; + u64 result = ~seed; + u8 const *c = cast(u8 const *)data; + for (remaining = len; remaining--; c++) { + result = (result >> 8) ^ (GB__CRC64_TABLE[(result ^ *c) & 0xff]); + } + return ~result; +} + +gb_internal bool check_if_exists_file_otherwise_create(String const &str) { + char const *str_c = alloc_cstring(permanent_allocator(), str); + if (!gb_file_exists(str_c)) { + gbFile f = {}; + gb_file_create(&f, str_c); + gb_file_close(&f); + return true; + } + return false; +} + + +gb_internal bool check_if_exists_directory_otherwise_create(String const &str) { +#if defined(GB_SYSTEM_WINDOWS) + String16 wstr = string_to_string16(permanent_allocator(), str); + wchar_t *wstr_c = alloc_wstring(permanent_allocator(), wstr); + return CreateDirectoryW(wstr_c, nullptr); +#else + char const *str_c = alloc_cstring(permanent_allocator(), str); + if (!gb_file_exists(str_c)) { + int status = mkdir(str_c, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + return status == 0; + } + return false; +#endif +} +gb_internal bool try_copy_executable_cache_internal(bool to_cache) { + String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); + defer (gb_free(heap_allocator(), exe_name.text)); + + gbString cache_name = gb_string_make(heap_allocator(), ""); + defer (gb_string_free(cache_name)); + + String cache_dir = build_context.build_cache_data.cache_dir; + + cache_name = gb_string_append_length(cache_name, cache_dir.text, cache_dir.len); + cache_name = gb_string_appendc(cache_name, "/"); + + cache_name = gb_string_appendc(cache_name, "cached-exe"); + if (selected_target_metrics) { + cache_name = gb_string_appendc(cache_name, "-"); + cache_name = gb_string_append_length(cache_name, selected_target_metrics->name.text, selected_target_metrics->name.len); + } + if (selected_subtarget) { + String st = subtarget_strings[selected_subtarget]; + cache_name = gb_string_appendc(cache_name, "-"); + cache_name = gb_string_append_length(cache_name, st.text, st.len); + } + cache_name = gb_string_appendc(cache_name, ".bin"); + + if (to_cache) { + return gb_file_copy( + alloc_cstring(temporary_allocator(), exe_name), + cache_name, + false + ); + } else { + return gb_file_copy( + cache_name, + alloc_cstring(temporary_allocator(), exe_name), + false + ); + } +} + + + +gb_internal bool try_copy_executable_to_cache(void) { + debugf("Cache: try_copy_executable_to_cache\n"); + + if (try_copy_executable_cache_internal(true)) { + build_context.build_cache_data.copy_already_done = true; + return true; + } + return false; +} + +gb_internal bool try_copy_executable_from_cache(void) { + debugf("Cache: try_copy_executable_from_cache\n"); + + if (try_copy_executable_cache_internal(false)) { + build_context.build_cache_data.copy_already_done = true; + return true; + } + return false; +} + + +#if !defined(GB_SYSTEM_WINDOWS) +extern char **environ; +#endif + +// returns false if different, true if it is the same +gb_internal bool try_cached_build(Checker *c, Array const &args) { + TEMPORARY_ALLOCATOR_GUARD(); + + Parser *p = c->parser; + + auto files = array_make(heap_allocator()); + for (AstPackage *pkg : p->packages) { + for (AstFile *f : pkg->files) { + array_add(&files, f->fullpath); + } + } + + #if defined(GB_SYSTEM_WINDOWS) + if (build_context.has_resource) { + String res_path = {}; + if (build_context.build_paths[BuildPath_RC].basename == "") { + res_path = path_to_string(permanent_allocator(), build_context.build_paths[BuildPath_RES]); + } else { + res_path = path_to_string(permanent_allocator(), build_context.build_paths[BuildPath_RC]); + } + array_add(&files, res_path); + } + #endif + + for (auto const &entry : c->info.load_file_cache) { + auto *cache = entry.value; + if (!cache || !cache->exists) { + continue; + } + array_add(&files, cache->path); + } + + array_sort(files, string_cmp); + + u64 crc = 0; + for (String const &path : files) { + crc = crc64_with_seed(path.text, path.len, crc); + } + + String base_cache_dir = build_context.build_paths[BuildPath_Output].basename; + base_cache_dir = concatenate_strings(permanent_allocator(), base_cache_dir, str_lit("/.odin-cache")); + (void)check_if_exists_directory_otherwise_create(base_cache_dir); + + gbString crc_str = gb_string_make_reserve(permanent_allocator(), 16); + crc_str = gb_string_append_fmt(crc_str, "%016llx", crc); + String cache_dir = concatenate3_strings(permanent_allocator(), base_cache_dir, str_lit("/"), make_string_c(crc_str)); + String files_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("files.manifest")); + String args_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("args.manifest")); + String env_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("env.manifest")); + + build_context.build_cache_data.cache_dir = cache_dir; + build_context.build_cache_data.files_path = files_path; + build_context.build_cache_data.args_path = args_path; + build_context.build_cache_data.env_path = env_path; + + auto envs = array_make(heap_allocator()); + defer (array_free(&envs)); + { + #if defined(GB_SYSTEM_WINDOWS) + wchar_t *strings = GetEnvironmentStringsW(); + defer (FreeEnvironmentStringsW(strings)); + + wchar_t *curr_string = strings; + while (curr_string && *curr_string) { + String16 wstr = make_string16_c(curr_string); + curr_string += wstr.len+1; + String str = string16_to_string(temporary_allocator(), wstr); + if (string_starts_with(str, str_lit("CURR_DATE_TIME="))) { + continue; + } + array_add(&envs, str); + } + #else + char **curr_env = environ; + while (curr_env && *curr_env) { + String str = make_string_c(*curr_env++); + if (string_starts_with(str, str_lit("PROMPT="))) { + continue; + } + if (string_starts_with(str, str_lit("RPROMPT="))) { + continue; + } + array_add(&envs, str); + } + #endif + } + array_sort(envs, string_cmp); + + if (check_if_exists_directory_otherwise_create(cache_dir)) { + goto write_cache; + } + + if (check_if_exists_file_otherwise_create(files_path)) { + goto write_cache; + } + if (check_if_exists_file_otherwise_create(args_path)) { + goto write_cache; + } + if (check_if_exists_file_otherwise_create(env_path)) { + goto write_cache; + } + + { + // exists already + LoadedFile loaded_file = {}; + + LoadedFileError file_err = load_file_32( + alloc_cstring(temporary_allocator(), files_path), + &loaded_file, + false + ); + if (file_err > LoadedFile_Empty) { + return false; + } + + String data = {cast(u8 *)loaded_file.data, loaded_file.size}; + String_Iterator it = {data, 0}; + + isize file_count = 0; + + for (; it.pos < data.len; file_count++) { + String line = string_split_iterator(&it, '\n'); + if (line.len == 0) { + break; + } + isize sep = string_index_byte(line, ' '); + if (sep < 0) { + goto write_cache; + } + + String timestamp_str = substring(line, 0, sep); + String path_str = substring(line, sep+1, line.len); + + timestamp_str = string_trim_whitespace(timestamp_str); + path_str = string_trim_whitespace(path_str); + + if (file_count >= files.count) { + goto write_cache; + } + if (files[file_count] != path_str) { + goto write_cache; + } + + u64 timestamp = exact_value_to_u64(exact_value_integer_from_string(timestamp_str)); + gbFileTime last_write_time = gb_file_last_write_time(alloc_cstring(temporary_allocator(), path_str)); + if (last_write_time != timestamp) { + goto write_cache; + } + } + + if (file_count != files.count) { + goto write_cache; + } + } + { + LoadedFile loaded_file = {}; + + LoadedFileError file_err = load_file_32( + alloc_cstring(temporary_allocator(), args_path), + &loaded_file, + false + ); + if (file_err > LoadedFile_Empty) { + return false; + } + + String data = {cast(u8 *)loaded_file.data, loaded_file.size}; + String_Iterator it = {data, 0}; + + isize args_count = 0; + + for (; it.pos < data.len; args_count++) { + String line = string_split_iterator(&it, '\n'); + line = string_trim_whitespace(line); + if (line.len == 0) { + break; + } + if (args_count >= args.count) { + goto write_cache; + } + + if (line != args[args_count]) { + goto write_cache; + } + } + } + { + LoadedFile loaded_file = {}; + + LoadedFileError file_err = load_file_32( + alloc_cstring(temporary_allocator(), env_path), + &loaded_file, + false + ); + if (file_err > LoadedFile_Empty) { + return false; + } + + String data = {cast(u8 *)loaded_file.data, loaded_file.size}; + String_Iterator it = {data, 0}; + + isize env_count = 0; + + for (; it.pos < data.len; env_count++) { + String line = string_split_iterator(&it, '\n'); + line = string_trim_whitespace(line); + if (line.len == 0) { + break; + } + if (env_count >= envs.count) { + goto write_cache; + } + + if (line != envs[env_count]) { + goto write_cache; + } + } + } + + return try_copy_executable_from_cache(); + +write_cache:; + { + char const *path_c = alloc_cstring(temporary_allocator(), files_path); + gb_file_remove(path_c); + + debugf("Cache: updating %s\n", path_c); + + gbFile f = {}; + defer (gb_file_close(&f)); + gb_file_open_mode(&f, gbFileMode_Write, path_c); + + for (String const &path : files) { + gbFileTime ft = gb_file_last_write_time(alloc_cstring(temporary_allocator(), path)); + gb_fprintf(&f, "%llu %.*s\n", cast(unsigned long long)ft, LIT(path)); + } + } + { + char const *path_c = alloc_cstring(temporary_allocator(), args_path); + gb_file_remove(path_c); + + debugf("Cache: updating %s\n", path_c); + + gbFile f = {}; + defer (gb_file_close(&f)); + gb_file_open_mode(&f, gbFileMode_Write, path_c); + + for (String const &arg : args) { + String targ = string_trim_whitespace(arg); + gb_fprintf(&f, "%.*s\n", LIT(targ)); + } + } + { + char const *path_c = alloc_cstring(temporary_allocator(), env_path); + gb_file_remove(path_c); + + debugf("Cache: updating %s\n", path_c); + + gbFile f = {}; + defer (gb_file_close(&f)); + gb_file_open_mode(&f, gbFileMode_Write, path_c); + + for (String const &env : envs) { + gb_fprintf(&f, "%.*s\n", LIT(env)); + } + } + + + return false; +} + diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index c3c217ec7..b6b1f9874 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1079,7 +1079,7 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return false; } -gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String const &original_string, bool err_on_not_found, LoadFileCache **cache_) { +gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String const &original_string, bool err_on_not_found, LoadFileCache **cache_, LoadFileTier tier, bool use_mutex=true) { ast_node(ce, CallExpr, call); ast_node(bd, BasicDirective, ce->proc); String builtin_name = bd->name.string; @@ -1092,19 +1092,30 @@ gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String BlockingMutex *ignore_mutex = nullptr; bool ok = determine_path_from_string(ignore_mutex, call, base_dir, original_string, &path); - gb_unused(ok); + if (!ok) { + if (err_on_not_found) { + error(ce->proc, "Failed to `#%.*s` file: %.*s; invalid file or cannot be found", LIT(builtin_name), LIT(original_string)); + } + call->state_flags |= StateFlag_DirectiveWasFalse; + return false; + } } - MUTEX_GUARD(&c->info->load_file_mutex); + if (use_mutex) mutex_lock(&c->info->load_file_mutex); + defer (if (use_mutex) mutex_unlock(&c->info->load_file_mutex)); gbFileError file_error = gbFileError_None; String data = {}; + bool exists = false; + LoadFileTier cache_tier = LoadFileTier_Invalid; LoadFileCache **cache_ptr = string_map_get(&c->info->load_file_cache, path); LoadFileCache *cache = cache_ptr ? *cache_ptr : nullptr; if (cache) { file_error = cache->file_error; data = cache->data; + exists = cache->exists; + cache_tier = cache->tier; } defer ({ if (cache == nullptr) { @@ -1112,60 +1123,78 @@ gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String new_cache->path = path; new_cache->data = data; new_cache->file_error = file_error; + new_cache->exists = exists; + new_cache->tier = cache_tier; string_map_init(&new_cache->hashes, 32); string_map_set(&c->info->load_file_cache, path, new_cache); if (cache_) *cache_ = new_cache; } else { cache->data = data; cache->file_error = file_error; + cache->exists = exists; + cache->tier = cache_tier; if (cache_) *cache_ = cache; } }); - TEMPORARY_ALLOCATOR_GUARD(); - char *c_str = alloc_cstring(temporary_allocator(), path); + if (tier > cache_tier) { + cache_tier = tier; - gbFile f = {}; - if (cache == nullptr) { + TEMPORARY_ALLOCATOR_GUARD(); + char *c_str = alloc_cstring(temporary_allocator(), path); + + gbFile f = {}; file_error = gb_file_open(&f, c_str); + defer (gb_file_close(&f)); + + if (file_error == gbFileError_None) { + exists = true; + + switch(tier) { + case LoadFileTier_Exists: + // Nothing to do. + break; + case LoadFileTier_Contents: { + isize file_size = cast(isize)gb_file_size(&f); + if (file_size > 0) { + u8 *ptr = cast(u8 *)gb_alloc(permanent_allocator(), file_size+1); + gb_file_read_at(&f, ptr, file_size, 0); + ptr[file_size] = '\0'; + data.text = ptr; + data.len = file_size; + } + break; + } + default: + GB_PANIC("Unhandled LoadFileTier"); + }; + } } - defer (gb_file_close(&f)); switch (file_error) { default: case gbFileError_Invalid: if (err_on_not_found) { - error(ce->proc, "Failed to `#%.*s` file: %s; invalid file or cannot be found", LIT(builtin_name), c_str); + error(ce->proc, "Failed to `#%.*s` file: %.*s; invalid file or cannot be found", LIT(builtin_name), LIT(path)); } call->state_flags |= StateFlag_DirectiveWasFalse; return false; case gbFileError_NotExists: if (err_on_not_found) { - error(ce->proc, "Failed to `#%.*s` file: %s; file cannot be found", LIT(builtin_name), c_str); + error(ce->proc, "Failed to `#%.*s` file: %.*s; file cannot be found", LIT(builtin_name), LIT(path)); } call->state_flags |= StateFlag_DirectiveWasFalse; return false; case gbFileError_Permission: if (err_on_not_found) { - error(ce->proc, "Failed to `#%.*s` file: %s; file permissions problem", LIT(builtin_name), c_str); + error(ce->proc, "Failed to `#%.*s` file: %.*s; file permissions problem", LIT(builtin_name), LIT(path)); } call->state_flags |= StateFlag_DirectiveWasFalse; return false; case gbFileError_None: // Okay break; - } - - if (cache == nullptr) { - isize file_size = cast(isize)gb_file_size(&f); - if (file_size > 0) { - u8 *ptr = cast(u8 *)gb_alloc(permanent_allocator(), file_size+1); - gb_file_read_at(&f, ptr, file_size, 0); - ptr[file_size] = '\0'; - data.text = ptr; - data.len = file_size; - } - } + }; return true; } @@ -1257,7 +1286,7 @@ gb_internal LoadDirectiveResult check_load_directive(CheckerContext *c, Operand operand->mode = Addressing_Constant; LoadFileCache *cache = nullptr; - if (cache_load_file_directive(c, call, o.value.value_string, err_on_not_found, &cache)) { + if (cache_load_file_directive(c, call, o.value.value_string, err_on_not_found, &cache, LoadFileTier_Contents)) { operand->value = exact_value_string(cache->data); return LoadDirective_Success; } @@ -1268,6 +1297,9 @@ gb_internal LoadDirectiveResult check_load_directive(CheckerContext *c, Operand gb_internal int file_cache_sort_cmp(void const *x, void const *y) { LoadFileCache const *a = *(LoadFileCache const **)(x); LoadFileCache const *b = *(LoadFileCache const **)(y); + if (a == b) { + return 0; + } return string_compare(a->path, b->path); } @@ -1339,6 +1371,8 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c map_set(&c->info->load_directory_map, call, new_cache); } else { cache->file_error = file_error; + + map_set(&c->info->load_directory_map, call, cache); } }); @@ -1381,9 +1415,12 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c file_caches = array_make(heap_allocator(), 0, files_to_reserve); + mutex_lock(&c->info->load_file_mutex); + defer (mutex_unlock(&c->info->load_file_mutex)); + for (FileInfo fi : list) { LoadFileCache *cache = nullptr; - if (cache_load_file_directive(c, call, fi.fullpath, err_on_not_found, &cache)) { + if (cache_load_file_directive(c, call, fi.fullpath, err_on_not_found, &cache, LoadFileTier_Contents, /*use_mutex*/false)) { array_add(&file_caches, cache); } else { result = LoadDirective_Error; @@ -1397,6 +1434,65 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c return result; } +gb_internal bool check_hash_kind(CheckerContext *c, Ast *call, String const &hash_kind, u8 const *data, isize data_size, u64 *hash_value) { + ast_node(ce, CallExpr, call); + ast_node(bd, BasicDirective, ce->proc); + String name = bd->name.string; + GB_ASSERT(name == "load_hash" || name == "hash"); + + String supported_hashes[] = { + str_lit("adler32"), + str_lit("crc32"), + str_lit("crc64"), + str_lit("fnv32"), + str_lit("fnv64"), + str_lit("fnv32a"), + str_lit("fnv64a"), + str_lit("murmur32"), + str_lit("murmur64"), + }; + + bool hash_found = false; + for (isize i = 0; i < gb_count_of(supported_hashes); i++) { + if (supported_hashes[i] == hash_kind) { + hash_found = true; + break; + } + } + if (!hash_found) { + ERROR_BLOCK(); + error(ce->proc, "Invalid hash kind passed to `#%.*s`, got: %.*s", LIT(name), LIT(hash_kind)); + error_line("\tAvailable hash kinds:\n"); + for (isize i = 0; i < gb_count_of(supported_hashes); i++) { + error_line("\t%.*s\n", LIT(supported_hashes[i])); + } + return false; + } + + if (hash_kind == "adler32") { + *hash_value = gb_adler32(data, data_size); + } else if (hash_kind == "crc32") { + *hash_value = gb_crc32(data, data_size); + } else if (hash_kind == "crc64") { + *hash_value = gb_crc64(data, data_size); + } else if (hash_kind == "fnv32") { + *hash_value = gb_fnv32(data, data_size); + } else if (hash_kind == "fnv64") { + *hash_value = gb_fnv64(data, data_size); + } else if (hash_kind == "fnv32a") { + *hash_value = fnv32a(data, data_size); + } else if (hash_kind == "fnv64a") { + *hash_value = fnv64a(data, data_size); + } else if (hash_kind == "murmur32") { + *hash_value = gb_murmur32(data, data_size); + } else if (hash_kind == "murmur64") { + *hash_value = gb_murmur64(data, data_size); + } else { + compiler_error("unhandled hash kind: %.*s", LIT(hash_kind)); + } + return true; +} + gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *operand, Ast *call, Type *type_hint) { @@ -1423,6 +1519,30 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o operand->type = t_source_code_location; operand->mode = Addressing_Value; + } else if (name == "exists") { + if (ce->args.count != 1) { + error(ce->close, "'#exists' expects 1 argument, got %td", ce->args.count); + return false; + } + + Operand o = {}; + check_expr(c, &o, ce->args[0]); + if (o.mode != Addressing_Constant || !is_type_string(o.type)) { + error(ce->args[0], "'#exists' expected a constant string argument"); + return false; + } + + operand->type = t_untyped_bool; + operand->mode = Addressing_Constant; + + String original_string = o.value.value_string; + LoadFileCache *cache = nullptr; + if (cache_load_file_directive(c, call, original_string, /* err_on_not_found=*/ false, &cache, LoadFileTier_Exists)) { + operand->value = exact_value_bool(cache->exists); + } else { + operand->value = exact_value_bool(false); + } + } else if (name == "load") { return check_load_directive(c, operand, call, type_hint, true) == LoadDirective_Success; } else if (name == "load_directory") { @@ -1474,37 +1594,8 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o String original_string = o.value.value_string; String hash_kind = o_hash.value.value_string; - String supported_hashes[] = { - str_lit("adler32"), - str_lit("crc32"), - str_lit("crc64"), - str_lit("fnv32"), - str_lit("fnv64"), - str_lit("fnv32a"), - str_lit("fnv64a"), - str_lit("murmur32"), - str_lit("murmur64"), - }; - - bool hash_found = false; - for (isize i = 0; i < gb_count_of(supported_hashes); i++) { - if (supported_hashes[i] == hash_kind) { - hash_found = true; - break; - } - } - if (!hash_found) { - ERROR_BLOCK(); - error(ce->proc, "Invalid hash kind passed to `#load_hash`, got: %.*s", LIT(hash_kind)); - error_line("\tAvailable hash kinds:\n"); - for (isize i = 0; i < gb_count_of(supported_hashes); i++) { - error_line("\t%.*s\n", LIT(supported_hashes[i])); - } - return false; - } - LoadFileCache *cache = nullptr; - if (cache_load_file_directive(c, call, original_string, true, &cache)) { + if (cache_load_file_directive(c, call, original_string, true, &cache, LoadFileTier_Contents)) { MUTEX_GUARD(&c->info->load_file_mutex); // TODO(bill): make these procedures fast :P u64 hash_value = 0; @@ -1514,26 +1605,9 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o } else { u8 *data = cache->data.text; isize file_size = cache->data.len; - if (hash_kind == "adler32") { - hash_value = gb_adler32(data, file_size); - } else if (hash_kind == "crc32") { - hash_value = gb_crc32(data, file_size); - } else if (hash_kind == "crc64") { - hash_value = gb_crc64(data, file_size); - } else if (hash_kind == "fnv32") { - hash_value = gb_fnv32(data, file_size); - } else if (hash_kind == "fnv64") { - hash_value = gb_fnv64(data, file_size); - } else if (hash_kind == "fnv32a") { - hash_value = fnv32a(data, file_size); - } else if (hash_kind == "fnv64a") { - hash_value = fnv64a(data, file_size); - } else if (hash_kind == "murmur32") { - hash_value = gb_murmur32(data, file_size); - } else if (hash_kind == "murmur64") { - hash_value = gb_murmur64(data, file_size); - } else { - compiler_error("unhandled hash kind: %.*s", LIT(hash_kind)); + + if (!check_hash_kind(c, call, hash_kind, data, file_size, &hash_value)) { + return false; } string_map_set(&cache->hashes, hash_kind, hash_value); } @@ -1544,6 +1618,62 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o return true; } return false; + } else if (name == "hash") { + if (ce->args.count != 2) { + if (ce->args.count == 0) { + error(ce->close, "'#hash' expects 2 argument, got 0"); + } else { + error(ce->args[0], "'#hash' expects 2 argument, got %td", ce->args.count); + } + return false; + } + + Ast *arg0 = ce->args[0]; + Ast *arg1 = ce->args[1]; + Operand o = {}; + check_expr(c, &o, arg0); + if (o.mode != Addressing_Constant) { + error(arg0, "'#hash' expected a constant string argument"); + return false; + } + + if (!is_type_string(o.type)) { + gbString str = type_to_string(o.type); + error(arg0, "'#hash' expected a constant string, got %s", str); + gb_string_free(str); + return false; + } + + Operand o_hash = {}; + check_expr(c, &o_hash, arg1); + if (o_hash.mode != Addressing_Constant) { + error(arg1, "'#hash' expected a constant string argument"); + return false; + } + + if (!is_type_string(o_hash.type)) { + gbString str = type_to_string(o.type); + error(arg1, "'#hash' expected a constant string, got %s", str); + gb_string_free(str); + return false; + } + gbAllocator a = heap_allocator(); + + GB_ASSERT(o.value.kind == ExactValue_String); + GB_ASSERT(o_hash.value.kind == ExactValue_String); + + String original_string = o.value.value_string; + String hash_kind = o_hash.value.value_string; + + // TODO: Cache hash values based off of string constant and hash kind? + u64 hash_value = 0; + if (check_hash_kind(c, call, hash_kind, original_string.text, original_string.len, &hash_value)) { + operand->type = t_untyped_integer; + operand->mode = Addressing_Constant; + operand->value = exact_value_u64(hash_value); + return true; + } + return false; } else if (name == "assert") { if (ce->args.count != 1 && ce->args.count != 2) { error(call, "'#assert' expects either 1 or 2 arguments, got %td", ce->args.count); @@ -1603,11 +1733,13 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o gb_string_free(str); return false; } - error(call, "Compile time panic: %.*s", LIT(operand->value.value_string)); - if (c->proc_name != "") { - gbString str = type_to_string(c->curr_proc_sig); - error_line("\tCalled within '%.*s' :: %s\n", LIT(c->proc_name), str); - gb_string_free(str); + if (!build_context.ignore_panic) { + error(call, "Compile time panic: %.*s", LIT(operand->value.value_string)); + if (c->proc_name != "") { + gbString str = type_to_string(c->curr_proc_sig); + error_line("\tCalled within '%.*s' :: %s\n", LIT(c->proc_name), str); + gb_string_free(str); + } } operand->type = t_invalid; operand->mode = Addressing_NoValue; @@ -1633,9 +1765,21 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o operand->mode = Addressing_Constant; operand->value = exact_value_bool(is_defined); + // If the arg is a selector expression we don't add it, `-define` only allows identifiers. + if (arg->kind == Ast_Ident) { + Defineable defineable = {}; + defineable.docs = nullptr; + defineable.name = arg->Ident.token.string; + defineable.default_value = exact_value_bool(false); + defineable.pos = arg->Ident.token.pos; + + MUTEX_GUARD(&c->info->defineables_mutex); + array_add(&c->info->defineables, defineable); + } + } else if (name == "config") { if (ce->args.count != 2) { - error(call, "'#config' expects 2 argument, got %td", ce->args.count); + error(call, "'#config' expects 2 arguments, got %td", ce->args.count); return false; } Ast *arg = unparen_expr(ce->args[0]); @@ -1670,6 +1814,20 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o operand->value = found->Constant.value; } } + + Defineable defineable = {}; + defineable.docs = nullptr; + defineable.name = name; + defineable.default_value = def.value; + defineable.pos = arg->Ident.token.pos; + + if (c->decl) { + defineable.docs = c->decl->docs; + } + + MUTEX_GUARD(&c->info->defineables_mutex); + array_add(&c->info->defineables, defineable); + } else { error(call, "Unknown directive call: #%.*s", LIT(name)); } @@ -1719,6 +1877,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_objc_register_selector: case BuiltinProc_objc_register_class: case BuiltinProc_atomic_type_is_lock_free: + case BuiltinProc_has_target_feature: + case BuiltinProc_procedure_of: // NOTE(bill): The first arg may be a Type, this will be checked case by case break; @@ -2022,6 +2182,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As Selection sel = lookup_field(type, field_name, false); if (sel.entity == nullptr) { + ERROR_BLOCK(); gbString type_str = type_to_string_shorthand(type); error(ce->args[0], "'%s' has no field named '%.*s'", type_str, LIT(field_name)); @@ -2095,6 +2256,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As Selection sel = lookup_field(type, field_name, false); if (sel.entity == nullptr) { + ERROR_BLOCK(); gbString type_str = type_to_string_shorthand(type); error(ce->args[0], "'%s' has no field named '%.*s'", type_str, LIT(field_name)); @@ -2135,6 +2297,14 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As error(o.expr, "Invalid argument to 'type_of'"); return false; } + + if (is_type_untyped(o.type)) { + gbString t = type_to_string(o.type); + error(o.expr, "'type_of' of %s cannot be determined", t); + gb_string_free(t); + return false; + } + // NOTE(bill): Prevent type cycles for procedure declarations if (c->curr_proc_sig == o.type) { gbString s = expr_to_string(o.expr); @@ -2293,6 +2463,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (arg_count > max_count) { error(call, "Too many 'swizzle' indices, %td > %td", arg_count, max_count); return false; + } else if (arg_count < 2) { + error(call, "Not enough 'swizzle' indices, %td < 2", arg_count); + return false; } if (type->kind == Type_Array) { @@ -3425,8 +3598,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As auto types = slice_make(permanent_allocator(), t->Struct.fields.count-1); for_array(i, types) { Entity *f = t->Struct.fields[i]; - GB_ASSERT(f->type->kind == Type_Pointer); - types[i] = alloc_type_slice(f->type->Pointer.elem); + GB_ASSERT(f->type->kind == Type_MultiPointer); + types[i] = alloc_type_slice(f->type->MultiPointer.elem); } operand->type = alloc_type_tuple_from_field_types(types.data, types.count, false, false); @@ -3663,6 +3836,41 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As break; } + case BuiltinProc_has_target_feature: { + String features = str_lit(""); + + check_expr_or_type(c, operand, ce->args[0]); + + if (is_type_string(operand->type) && operand->mode == Addressing_Constant) { + GB_ASSERT(operand->value.kind == ExactValue_String); + features = operand->value.value_string; + } else { + Type *pt = base_type(operand->type); + if (pt->kind == Type_Proc) { + if (pt->Proc.require_target_feature.len != 0) { + GB_ASSERT(pt->Proc.enable_target_feature.len == 0); + features = pt->Proc.require_target_feature; + } else if (pt->Proc.enable_target_feature.len != 0) { + features = pt->Proc.enable_target_feature; + } else { + error(ce->args[0], "Expected the procedure type given to '%.*s' to have @(require_target_feature=\"...\") or @(enable_target_feature=\"...\")", LIT(builtin_name)); + } + } else { + error(ce->args[0], "Expected a constant string or procedure type for '%.*s'", LIT(builtin_name)); + } + } + + String invalid; + if (!check_target_feature_is_valid_globally(features, &invalid)) { + error(ce->args[0], "Target feature '%.*s' is not a valid target feature", LIT(invalid)); + } + + operand->value = exact_value_bool(check_target_feature_is_enabled(features, nullptr)); + operand->mode = Addressing_Constant; + operand->type = t_untyped_bool; + break; + } + case BuiltinProc_soa_struct: { Operand x = {}; Operand y = {}; @@ -4094,6 +4302,49 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } break; + case BuiltinProc_add_sat: + case BuiltinProc_sub_sat: + { + Operand x = {}; + Operand y = {}; + check_expr(c, &x, ce->args[0]); + check_expr(c, &y, ce->args[1]); + if (x.mode == Addressing_Invalid) { + return false; + } + if (y.mode == Addressing_Invalid) { + return false; + } + convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; + convert_to_typed(c, &x, y.type); + if (is_type_untyped(x.type)) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected a typed integer for '%.*s', got %s", LIT(builtin_name), xts); + gb_string_free(xts); + return false; + } + if (!is_type_integer(x.type)) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected an integer for '%.*s', got %s", LIT(builtin_name), xts); + gb_string_free(xts); + return false; + } + Type *ct = core_type(x.type); + if (is_type_different_to_arch_endianness(ct)) { + GB_ASSERT(ct->kind == Type_Basic); + if (ct->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected an integer which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts); + gb_string_free(xts); + return false; + } + } + + operand->mode = Addressing_Value; + operand->type = default_type(x.type); + } + break; + case BuiltinProc_sqrt: { Operand x = {}; @@ -4924,15 +5175,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As isize max_arg_count = 32; switch (build_context.metrics.os) { - case TargetOs_windows: - case TargetOs_freestanding: - error(call, "'%.*s' is not supported on this platform (%.*s)", LIT(builtin_name), LIT(target_os_names[build_context.metrics.os])); - break; case TargetOs_darwin: case TargetOs_linux: case TargetOs_essence: - case TargetOs_freebsd: - case TargetOs_openbsd: case TargetOs_haiku: switch (build_context.metrics.arch) { case TargetArch_i386: @@ -4942,6 +5187,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As break; } break; + default: + error(call, "'%.*s' is not supported on this platform (%.*s)", LIT(builtin_name), LIT(target_os_names[build_context.metrics.os])); + break; } if (ce->args.count > max_arg_count) { @@ -4955,6 +5203,55 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return true; } break; + case BuiltinProc_syscall_bsd: + { + convert_to_typed(c, operand, t_uintptr); + if (!is_type_uintptr(operand->type)) { + gbString t = type_to_string(operand->type); + error(operand->expr, "Argument 0 must be of type 'uintptr', got %s", t); + gb_string_free(t); + } + for (isize i = 1; i < ce->args.count; i++) { + Operand x = {}; + check_expr(c, &x, ce->args[i]); + if (x.mode != Addressing_Invalid) { + convert_to_typed(c, &x, t_uintptr); + } + convert_to_typed(c, &x, t_uintptr); + if (!is_type_uintptr(x.type)) { + gbString t = type_to_string(x.type); + error(x.expr, "Argument %td must be of type 'uintptr', got %s", i, t); + gb_string_free(t); + } + } + + isize max_arg_count = 32; + + switch (build_context.metrics.os) { + case TargetOs_freebsd: + case TargetOs_netbsd: + case TargetOs_openbsd: + switch (build_context.metrics.arch) { + case TargetArch_amd64: + case TargetArch_arm64: + max_arg_count = 7; + break; + } + break; + default: + error(call, "'%.*s' is not supported on this platform (%.*s)", LIT(builtin_name), LIT(target_os_names[build_context.metrics.os])); + break; + } + + if (ce->args.count > max_arg_count) { + error(ast_end_token(call), "'%.*s' has a maximum of %td arguments on this platform (%.*s), got %td", LIT(builtin_name), max_arg_count, LIT(target_os_names[build_context.metrics.os]), ce->args.count); + } + + operand->mode = Addressing_Value; + operand->type = make_optional_ok_type(t_uintptr); + return true; + } + break; case BuiltinProc_type_base_type: @@ -5183,6 +5480,34 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As operand->type = t_untyped_bool; break; + + case BuiltinProc_type_is_matrix_row_major: + case BuiltinProc_type_is_matrix_column_major: + { + Operand op = {}; + Type *bt = check_type(c, ce->args[0]); + Type *type = base_type(bt); + if (type == nullptr || type == t_invalid) { + error(ce->args[0], "Expected a type for '%.*s'", LIT(builtin_name)); + return false; + } + if (type->kind != Type_Matrix) { + gbString s = type_to_string(bt); + error(ce->args[0], "Expected a matrix type for '%.*s', got '%s'", LIT(builtin_name), s); + gb_string_free(s); + return false; + } + + if (id == BuiltinProc_type_is_matrix_row_major) { + operand->value = exact_value_bool(bt->Matrix.is_row_major == true); + } else { + operand->value = exact_value_bool(bt->Matrix.is_row_major == false); + } + operand->mode = Addressing_Constant; + operand->type = t_untyped_bool; + break; + } + case BuiltinProc_type_has_field: { Operand op = {}; @@ -5395,6 +5720,58 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As operand->value = exact_value_i64(u->Union.kind == UnionType_no_nil ? 0 : 1); } break; + case BuiltinProc_type_bit_set_elem_type: + { + + if (operand->mode != Addressing_Type) { + error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name)); + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + return false; + } + + Type *bs = operand->type; + + if (!is_type_bit_set(bs)) { + error(operand->expr, "Expected a bit_set type for '%.*s'", LIT(builtin_name)); + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + return false; + } + + bs = base_type(bs); + GB_ASSERT(bs->kind == Type_BitSet); + + operand->mode = Addressing_Type; + operand->type = bs->BitSet.elem; + } break; + + case BuiltinProc_type_bit_set_underlying_type: + { + + if (operand->mode != Addressing_Type) { + error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name)); + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + return false; + } + + Type *bs = operand->type; + + if (!is_type_bit_set(bs)) { + error(operand->expr, "Expected a bit_set type for '%.*s'", LIT(builtin_name)); + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + return false; + } + + bs = base_type(bs); + GB_ASSERT(bs->kind == Type_BitSet); + + operand->mode = Addressing_Type; + operand->type = bit_set_to_int(bs); + } break; + case BuiltinProc_type_union_variant_count: { if (operand->mode != Addressing_Type) { @@ -5520,6 +5897,31 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As operand->mode = Addressing_Constant; operand->type = t_untyped_integer; break; + case BuiltinProc_type_struct_has_implicit_padding: + operand->value = exact_value_bool(false); + if (operand->mode != Addressing_Type) { + error(operand->expr, "Expected a struct type for '%.*s'", LIT(builtin_name)); + } else if (!is_type_struct(operand->type) && !is_type_soa_struct(operand->type)) { + error(operand->expr, "Expected a struct type for '%.*s'", LIT(builtin_name)); + } else { + Type *bt = base_type(operand->type); + if (bt->Struct.is_packed) { + operand->value = exact_value_bool(false); + } else if (bt->Struct.fields.count != 0) { + i64 size = type_size_of(bt); + Type *field_type = nullptr; + i64 last_offset = type_offset_of(bt, bt->Struct.fields.count-1, &field_type); + if (last_offset+type_size_of(field_type) < size) { + operand->value = exact_value_bool(true); + } else { + i64 packed_size = type_size_of_struct_pretend_is_packed(bt); + operand->value = exact_value_bool(packed_size < size); + } + } + } + operand->mode = Addressing_Constant; + operand->type = t_untyped_bool; + break; case BuiltinProc_type_proc_parameter_count: operand->value = exact_value_i64(0); @@ -5671,15 +6073,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (operand->mode != Addressing_Type) { error(operand->expr, "Expected a record type for '%.*s'", LIT(builtin_name)); } else { - Type *bt = base_type(operand->type); - if (bt->kind == Type_Struct) { - if (bt->Struct.polymorphic_params != nullptr) { - operand->value = exact_value_i64(bt->Struct.polymorphic_params->Tuple.variables.count); - } - } else if (bt->kind == Type_Union) { - if (bt->Union.polymorphic_params != nullptr) { - operand->value = exact_value_i64(bt->Union.polymorphic_params->Tuple.variables.count); - } + TypeTuple *tuple = get_record_polymorphic_params(operand->type); + if (tuple) { + operand->value = exact_value_i64(tuple->variables.count); } else { error(operand->expr, "Expected a record type for '%.*s'", LIT(builtin_name)); } @@ -5711,20 +6107,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As Entity *param = nullptr; i64 count = 0; - Type *bt = base_type(operand->type); - if (bt->kind == Type_Struct) { - if (bt->Struct.polymorphic_params != nullptr) { - count = bt->Struct.polymorphic_params->Tuple.variables.count; - if (index < count) { - param = bt->Struct.polymorphic_params->Tuple.variables[cast(isize)index]; - } - } - } else if (bt->kind == Type_Union) { - if (bt->Union.polymorphic_params != nullptr) { - count = bt->Union.polymorphic_params->Tuple.variables.count; - if (index < count) { - param = bt->Union.polymorphic_params->Tuple.variables[cast(isize)index]; - } + TypeTuple *tuple = get_record_polymorphic_params(operand->type); + if (tuple) { + count = tuple->variables.count; + if (index < count) { + param = tuple->variables[cast(isize)index]; } } else { error(operand->expr, "Expected a specialized polymorphic record type for '%.*s'", LIT(builtin_name)); @@ -5801,6 +6188,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As Selection sel = lookup_field(type, field_name, false); if (sel.entity == nullptr) { + ERROR_BLOCK(); gbString type_str = type_to_string(bt); error(ce->args[0], "'%s' has no field named '%.*s'", type_str, LIT(field_name)); @@ -5927,6 +6315,51 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As break; } + case BuiltinProc_procedure_of: + { + Ast *call_expr = unparen_expr(ce->args[0]); + Operand op = {}; + check_expr_base(c, &op, ce->args[0], nullptr); + if (op.mode != Addressing_Value && !(call_expr && call_expr->kind == Ast_CallExpr)) { + error(ce->args[0], "Expected a call expression for '%.*s'", LIT(builtin_name)); + return false; + } + + Ast *proc = call_expr->CallExpr.proc; + Entity *e = entity_of_node(proc); + + if (e == nullptr) { + error(ce->args[0], "Invalid procedure value, expected a regular/specialized procedure"); + return false; + } + + TypeAndValue tav = proc->tav; + + + operand->type = e->type; + operand->mode = Addressing_Value; + operand->value = tav.value; + operand->builtin_id = BuiltinProc_Invalid; + operand->proc_group = nullptr; + + if (tav.mode == Addressing_Builtin) { + operand->mode = tav.mode; + operand->builtin_id = cast(BuiltinProcId)e->Builtin.id; + break; + } + + if (!is_type_proc(e->type)) { + gbString s = type_to_string(e->type); + error(ce->args[0], "Expected a procedure value, got '%s'", s); + gb_string_free(s); + return false; + } + + + ce->entity_procedure_of = e; + break; + } + case BuiltinProc_constant_utf16_cstring: { String value = {}; @@ -6014,7 +6447,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } - enable_target_feature({}, str_lit("atomics")); + if (!check_target_feature_is_enabled(str_lit("atomics"), nullptr)) { + error(call, "'%.*s' requires target feature 'atomics' to be enabled, enable it with -target-features:\"atomics\" or choose a different -microarch", LIT(builtin_name)); + return false; + } Operand ptr = {}; Operand expected = {}; @@ -6068,7 +6504,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } - enable_target_feature({}, str_lit("atomics")); + if (!check_target_feature_is_enabled(str_lit("atomics"), nullptr)) { + error(call, "'%.*s' requires target feature 'atomics' to be enabled, enable it with -target-features:\"atomics\" or choose a different -microarch", LIT(builtin_name)); + return false; + } Operand ptr = {}; Operand waiters = {}; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 952a877a4..a1436fe03 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -88,11 +88,17 @@ gb_internal Type *check_init_variable(CheckerContext *ctx, Entity *e, Operand *o e->type = t_invalid; return nullptr; } else if (is_type_polymorphic(t)) { - gbString str = type_to_string(t); - defer (gb_string_free(str)); - error(e->token, "Invalid use of a polymorphic type '%s' in %.*s", str, LIT(context_name)); - e->type = t_invalid; - return nullptr; + Entity *e = entity_of_node(operand->expr); + if (e == nullptr) { + return nullptr; + } + if (e->state.load() != EntityState_Resolved) { + gbString str = type_to_string(t); + defer (gb_string_free(str)); + error(e->token, "Invalid use of a polymorphic type '%s' in %.*s", str, LIT(context_name)); + e->type = t_invalid; + return nullptr; + } } else if (is_type_empty_union(t)) { gbString str = type_to_string(t); defer (gb_string_free(str)); @@ -149,6 +155,154 @@ gb_internal void check_init_variables(CheckerContext *ctx, Entity **lhs, isize l } } + +gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_entity) { + // NOTE(bill): The original_entity's scope may not be same scope that it was inserted into + // e.g. file entity inserted into its package scope + String original_name = original_entity->token.string; + Scope *found_scope = nullptr; + Entity *found_entity = nullptr; + scope_lookup_parent(original_entity->scope, original_name, &found_scope, &found_entity); + if (found_scope == nullptr) { + return; + } + rw_mutex_lock(&found_scope->mutex); + defer (rw_mutex_unlock(&found_scope->mutex)); + + // IMPORTANT NOTE(bill, 2021-04-10): Overriding behaviour was flawed in that the + // original entity was still used check checked, but the checking was only + // relying on "constant" data such as the Entity.type and Entity.Constant.value + // + // Therefore two things can be done: the type can be assigned to state that it + // has been "evaluated" and the variant data can be copied across + + string_map_set(&found_scope->elements, original_name, new_entity); + + original_entity->flags |= EntityFlag_Overridden; + original_entity->type = new_entity->type; + original_entity->aliased_of = new_entity; + + original_entity->identifier.store(new_entity->identifier); + + if (original_entity->identifier.load() != nullptr && + original_entity->identifier.load()->kind == Ast_Ident) { + original_entity->identifier.load()->Ident.entity = new_entity; + } + + // IMPORTANT NOTE(bill, 2021-04-10): copy only the variants + // This is most likely NEVER required, but it does not at all hurt to keep + isize offset = cast(u8 *)&original_entity->Dummy.start - cast(u8 *)original_entity; + isize size = gb_size_of(*original_entity) - offset; + gb_memmove(cast(u8 *)original_entity, cast(u8 *)new_entity, size); +} + +gb_internal bool check_override_as_type_due_to_aliasing(CheckerContext *ctx, Entity *e, Entity *entity, Ast *init, Type *named_type) { + if (entity != nullptr && entity->kind == Entity_TypeName) { + // @TypeAliasingProblem + // NOTE(bill, 2022-02-03): This is used to solve the problem caused by type aliases + // being "confused" as constants + // + // A :: B + // C :: proc "c" (^A) + // B :: struct {x: C} + // + // A gets evaluated first, and then checks B. + // B then checks C. + // C then tries to check A which is unresolved but thought to be a constant. + // Therefore within C's check, A errs as "not a type". + // + // This is because a const declaration may or may not be a type and this cannot + // be determined from a syntactical standpoint. + // This check allows the compiler to override the entity to be checked as a type. + // + // There is no problem if B is prefixed with the `#type` helper enforcing at + // both a syntax and semantic level that B must be a type. + // + // A :: #type B + // + // This approach is not fool proof and can fail in case such as: + // + // X :: type_of(x) + // X :: Foo(int).Type + // + // Since even these kind of declarations may cause weird checking cycles. + // For the time being, these are going to be treated as an unfortunate error + // until there is a proper delaying system to try declaration again if they + // have failed. + + e->kind = Entity_TypeName; + check_type_decl(ctx, e, init, named_type); + return true; + } + return false; +} + +gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d); + +gb_internal bool check_try_override_const_decl(CheckerContext *ctx, Entity *e, Entity *entity, Ast *init, Type *named_type) { + if (entity == nullptr) { + retry_proc_lit:; + init = unparen_expr(init); + if (init == nullptr) { + return false; + } + if (init->kind == Ast_TernaryWhenExpr) { + ast_node(we, TernaryWhenExpr, init); + if (we->cond == nullptr) { + return false; + } + if (we->cond->tav.value.kind != ExactValue_Bool) { + return false; + } + init = we->cond->tav.value.value_bool ? we->x : we->y; + goto retry_proc_lit; + } if (init->kind == Ast_ProcLit) { + // NOTE(bill, 2024-07-04): Override as a procedure entity because this could be within a `when` statement + e->kind = Entity_Procedure; + e->type = nullptr; + DeclInfo *d = decl_info_of_entity(e); + d->proc_lit = init; + check_proc_decl(ctx, e, d); + return true; + } + + return false; + } + switch (entity->kind) { + case Entity_TypeName: + if (check_override_as_type_due_to_aliasing(ctx, e, entity, init, named_type)) { + return true; + } + break; + case Entity_Builtin: + if (e->type != nullptr) { + return false; + } + e->kind = Entity_Builtin; + e->Builtin.id = entity->Builtin.id; + e->type = t_invalid; + return true; + } + + if (e->type != nullptr && entity->type != nullptr) { + Operand x = {}; + x.type = entity->type; + x.mode = Addressing_Variable; + if (!check_is_assignable_to(ctx, &x, e->type)) { + return false; + } + } + + // NOTE(bill): Override aliased entity + switch (entity->kind) { + case Entity_ProcGroup: + case Entity_Procedure: + override_entity_in_scope(e, entity); + return true; + } + return false; +} + gb_internal void check_init_constant(CheckerContext *ctx, Entity *e, Operand *operand) { if (operand->mode == Addressing_Invalid || operand->type == t_invalid || @@ -159,6 +313,13 @@ gb_internal void check_init_constant(CheckerContext *ctx, Entity *e, Operand *op return; } + if (operand->mode != Addressing_Constant) { + Entity *entity = entity_of_node(operand->expr); + if (check_try_override_const_decl(ctx, e, entity, operand->expr, nullptr)) { + return; + } + } + if (operand->mode != Addressing_Constant) { gbString str = expr_to_string(operand->expr); error(operand->expr, "'%s' is not a compile-time known constant", str); @@ -367,49 +528,6 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, } -gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_entity) { - // NOTE(bill): The original_entity's scope may not be same scope that it was inserted into - // e.g. file entity inserted into its package scope - String original_name = original_entity->token.string; - Scope *found_scope = nullptr; - Entity *found_entity = nullptr; - scope_lookup_parent(original_entity->scope, original_name, &found_scope, &found_entity); - if (found_scope == nullptr) { - return; - } - rw_mutex_lock(&found_scope->mutex); - defer (rw_mutex_unlock(&found_scope->mutex)); - - // IMPORTANT NOTE(bill, 2021-04-10): Overriding behaviour was flawed in that the - // original entity was still used check checked, but the checking was only - // relying on "constant" data such as the Entity.type and Entity.Constant.value - // - // Therefore two things can be done: the type can be assigned to state that it - // has been "evaluated" and the variant data can be copied across - - string_map_set(&found_scope->elements, original_name, new_entity); - - original_entity->flags |= EntityFlag_Overridden; - original_entity->type = new_entity->type; - original_entity->aliased_of = new_entity; - - Ast *empty_ident = nullptr; - original_entity->identifier.compare_exchange_strong(empty_ident, new_entity->identifier); - - if (original_entity->identifier.load() != nullptr && - original_entity->identifier.load()->kind == Ast_Ident) { - original_entity->identifier.load()->Ident.entity = new_entity; - } - - // IMPORTANT NOTE(bill, 2021-04-10): copy only the variants - // This is most likely NEVER required, but it does not at all hurt to keep - isize offset = cast(u8 *)&original_entity->Dummy.start - cast(u8 *)original_entity; - isize size = gb_size_of(*original_entity) - offset; - gb_memmove(cast(u8 *)original_entity, cast(u8 *)new_entity, size); -} - - - gb_internal void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr, Ast *init, Type *named_type) { GB_ASSERT(e->type == nullptr); GB_ASSERT(e->kind == Entity_Constant); @@ -435,41 +553,7 @@ gb_internal void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr if (init != nullptr) { Entity *entity = check_entity_from_ident_or_selector(ctx, init, false); - if (entity != nullptr && entity->kind == Entity_TypeName) { - // @TypeAliasingProblem - // NOTE(bill, 2022-02-03): This is used to solve the problem caused by type aliases - // being "confused" as constants - // - // A :: B - // C :: proc "c" (^A) - // B :: struct {x: C} - // - // A gets evaluated first, and then checks B. - // B then checks C. - // C then tries to check A which is unresolved but thought to be a constant. - // Therefore within C's check, A errs as "not a type". - // - // This is because a const declaration may or may not be a type and this cannot - // be determined from a syntactical standpoint. - // This check allows the compiler to override the entity to be checked as a type. - // - // There is no problem if B is prefixed with the `#type` helper enforcing at - // both a syntax and semantic level that B must be a type. - // - // A :: #type B - // - // This approach is not fool proof and can fail in case such as: - // - // X :: type_of(x) - // X :: Foo(int).Type - // - // Since even these kind of declarations may cause weird checking cycles. - // For the time being, these are going to be treated as an unfortunate error - // until there is a proper delaying system to try declaration again if they - // have failed. - - e->kind = Entity_TypeName; - check_type_decl(ctx, e, init, named_type); + if (check_override_as_type_due_to_aliasing(ctx, e, entity, init, named_type)) { return; } entity = nullptr; @@ -479,6 +563,9 @@ gb_internal void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr entity = check_selector(ctx, &operand, init, e->type); } else { check_expr_or_type(ctx, &operand, init, e->type); + if (init->kind == Ast_CallExpr) { + entity = init->CallExpr.entity_procedure_of; + } } switch (operand.mode) { @@ -526,6 +613,7 @@ gb_internal void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr return; } + if (entity != nullptr) { if (e->type != nullptr) { Operand x = {}; @@ -699,7 +787,7 @@ gb_internal Entity *init_entity_foreign_library(CheckerContext *ctx, Entity *e) } if (ident == nullptr) { - error(e->token, "foreign entiies must declare which library they are from"); + error(e->token, "foreign entities must declare which library they are from"); } else if (ident->kind != Ast_Ident) { error(ident, "foreign library names must be an identifier"); } else { @@ -724,9 +812,10 @@ gb_internal Entity *init_entity_foreign_library(CheckerContext *ctx, Entity *e) return nullptr; } -gb_internal String handle_link_name(CheckerContext *ctx, Token token, String link_name, String link_prefix) { +gb_internal String handle_link_name(CheckerContext *ctx, Token token, String link_name, String link_prefix, String link_suffix) { + String original_link_name = link_name; if (link_prefix.len > 0) { - if (link_name.len > 0) { + if (original_link_name.len > 0) { error(token, "'link_name' and 'link_prefix' cannot be used together"); } else { isize len = link_prefix.len + token.string.len; @@ -738,9 +827,28 @@ gb_internal String handle_link_name(CheckerContext *ctx, Token token, String lin link_name = make_string(name, len); } } + + if (link_suffix.len > 0) { + if (original_link_name.len > 0) { + error(token, "'link_name' and 'link_suffix' cannot be used together"); + } else { + String new_name = token.string; + if (link_name != original_link_name) { + new_name = link_name; + } + + isize len = new_name.len + link_suffix.len; + u8 *name = gb_alloc_array(permanent_allocator(), u8, len+1); + gb_memmove(name, &new_name[0], new_name.len); + gb_memmove(name+new_name.len, &link_suffix[0], link_suffix.len); + name[len] = 0; + link_name = make_string(name, len); + } + } return link_name; } + gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeContext const &ac) { if (!(ac.objc_name.len || ac.objc_is_class_method || ac.objc_type)) { return; @@ -862,7 +970,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } TypeProc *pt = &proc_type->Proc; - AttributeContext ac = make_attribute_context(e->Procedure.link_prefix); + AttributeContext ac = make_attribute_context(e->Procedure.link_prefix, e->Procedure.link_suffix); if (d != nullptr) { check_decl_attributes(ctx, d->attributes, proc_decl_attribute, &ac); @@ -886,22 +994,41 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { check_objc_methods(ctx, e, ac); - if (ac.require_target_feature.len != 0 && ac.enable_target_feature.len != 0) { - error(e->token, "Attributes @(require_target_feature=...) and @(enable_target_feature=...) cannot be used together"); - } else if (ac.require_target_feature.len != 0) { - if (check_target_feature_is_enabled(e->token.pos, ac.require_target_feature)) { - e->Procedure.target_feature = ac.require_target_feature; - } else { - e->Procedure.target_feature_disabled = true; + { + if (ac.require_target_feature.len != 0 && ac.enable_target_feature.len != 0) { + error(e->token, "A procedure cannot have both @(require_target_feature=\"...\") and @(enable_target_feature=\"...\")"); + } + + if (build_context.strict_target_features && ac.enable_target_feature.len != 0) { + ac.require_target_feature = ac.enable_target_feature; + ac.enable_target_feature.len = 0; + } + + if (ac.require_target_feature.len != 0) { + pt->require_target_feature = ac.require_target_feature; + String invalid; + if (!check_target_feature_is_valid_globally(ac.require_target_feature, &invalid)) { + error(e->token, "Required target feature '%.*s' is not a valid target feature", LIT(invalid)); + } else if (!check_target_feature_is_enabled(ac.require_target_feature, nullptr)) { + e->flags |= EntityFlag_Disabled; + } + } else if (ac.enable_target_feature.len != 0) { + + // NOTE: disallow wasm, features on that arch are always global to the module. + if (is_arch_wasm()) { + error(e->token, "@(enable_target_feature=\"...\") is not allowed on wasm, features for wasm must be declared globally"); + } + + pt->enable_target_feature = ac.enable_target_feature; + String invalid; + if (!check_target_feature_is_valid_globally(ac.enable_target_feature, &invalid)) { + error(e->token, "Procedure enabled target feature '%.*s' is not a valid target feature", LIT(invalid)); + } } - } else if (ac.enable_target_feature.len != 0) { - enable_target_feature(e->token.pos, ac.enable_target_feature); - e->Procedure.target_feature = ac.enable_target_feature; } switch (e->Procedure.optimization_mode) { case ProcedureOptimizationMode_None: - case ProcedureOptimizationMode_Minimal: if (pl->inlining == ProcInlining_inline) { error(e->token, "#force_inline cannot be used in conjunction with the attribute 'optimization_mode' with neither \"none\" nor \"minimal\""); } @@ -995,7 +1122,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_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; @@ -1027,7 +1154,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } - if (e->pkg != nullptr && e->token.string == "main") { + if (e->pkg != nullptr && e->token.string == "main" && !build_context.no_entry_point) { if (e->pkg->kind != Package_Runtime) { if (pt->param_count != 0 || pt->result_count != 0) { @@ -1095,7 +1222,14 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } if (ac.link_name.len > 0) { - e->Procedure.link_name = ac.link_name; + String ln = ac.link_name; + e->Procedure.link_name = ln; + if (ln == "memcpy" || + ln == "memmove" || + ln == "mem_copy" || + ln == "mem_copy_non_overlapping") { + e->Procedure.is_memcpy_like = true; + } } if (ac.deferred_procedure.entity != nullptr) { @@ -1121,9 +1255,12 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { if (foreign_library->LibraryName.paths.count >= 1) { module_name = foreign_library->LibraryName.paths[0]; } - name = concatenate3_strings(permanent_allocator(), module_name, WASM_MODULE_NAME_SEPARATOR, name); + + if (!string_ends_with(module_name, str_lit(".o"))) { + name = concatenate3_strings(permanent_allocator(), module_name, WASM_MODULE_NAME_SEPARATOR, name); + } } - + e->Procedure.is_foreign = true; e->Procedure.link_name = name; @@ -1203,7 +1340,7 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast } e->flags |= EntityFlag_Visited; - AttributeContext ac = make_attribute_context(e->Variable.link_prefix); + AttributeContext ac = make_attribute_context(e->Variable.link_prefix, e->Variable.link_suffix); ac.init_expr_list_count = init_expr != nullptr ? 1 : 0; DeclInfo *decl = decl_info_of_entity(e); @@ -1224,7 +1361,10 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast if (ac.is_static) { error(e->token, "@(static) is not supported for global variables, nor required"); } - ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix); + if (ac.rodata) { + e->Variable.is_rodata = true; + } + ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix, ac.link_suffix); if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) { e->Variable.thread_local_model.len = 0; @@ -1260,8 +1400,8 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast error(e->token, "A foreign variable declaration cannot have a default value"); } init_entity_foreign_library(ctx, e); - if (is_arch_wasm()) { - error(e->token, "A foreign variable declaration are not allowed for the '%.*s' architecture", LIT(target_arch_names[build_context.metrics.arch])); + if (is_arch_wasm() && e->Variable.foreign_library != nullptr) { + error(e->token, "A foreign variable declaration can not be scoped to a module and must be declared in a 'foreign {' (without a library) block"); } } if (ac.link_name.len > 0) { @@ -1310,6 +1450,9 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast Operand o = {}; check_expr_with_type_hint(ctx, &o, init_expr, e->type); check_init_variable(ctx, e, &o, str_lit("variable declaration")); + if (e->Variable.is_rodata && o.mode != Addressing_Constant) { + error(o.expr, "Variables declared with @(rodata) must have constant initialization"); + } check_rtti_type_disallowed(e->token, e->type, "A variable declaration is using a type, %s, which has been disallowed"); } @@ -1370,6 +1513,10 @@ gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, D continue; } + if (p->flags & EntityFlag_Disabled) { + continue; + } + String name = p->token.string; for (isize k = j+1; k < pge->entities.count; k++) { @@ -1387,6 +1534,10 @@ gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, D ERROR_BLOCK(); + if (q->flags & EntityFlag_Disabled) { + continue; + } + ProcTypeOverloadKind kind = are_proc_types_overload_safe(p->type, q->type); bool both_have_where_clauses = false; if (p->decl_info->proc_lit != nullptr && q->decl_info->proc_lit != nullptr) { @@ -1423,6 +1574,7 @@ gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, D break; case ProcOverload_ParamCount: case ProcOverload_ParamTypes: + case ProcOverload_TargetFeatures: // This is okay :) break; @@ -1590,6 +1742,17 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de if (e->kind != Entity_Variable) { continue; } + if (is_type_polymorphic(e->type) && is_type_polymorphic_record_unspecialized(e->type)) { + gbString s = type_to_string(e->type); + char const *msg = "Unspecialized polymorphic types are not allowed in procedure parameters, got %s"; + if (e->Variable.type_expr) { + error(e->Variable.type_expr, msg, s); + } else { + error(e->token, msg, s); + } + gb_string_free(s); + } + if (!(e->flags & EntityFlag_Using)) { continue; } @@ -1705,5 +1868,14 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de add_deps_from_child_to_parent(decl); + for (VariadicReuseData const &vr : decl->variadic_reuses) { + GB_ASSERT(vr.slice_type->kind == Type_Slice); + Type *elem = vr.slice_type->Slice.elem; + i64 size = type_size_of(elem); + i64 align = type_align_of(elem); + decl->variadic_reuse_max_bytes = gb_max(decl->variadic_reuse_max_bytes, size*vr.max_count); + decl->variadic_reuse_max_align = gb_max(decl->variadic_reuse_max_align, align); + } + return true; } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index b893b3a00..01ff9da5b 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -102,6 +102,7 @@ gb_internal Type * check_init_variable (CheckerContext *c, Entity * gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o, Type *type, i64 max_bit_size=0); gb_internal void add_map_key_type_dependencies(CheckerContext *ctx, Type *key); +gb_internal Type *make_soa_struct_fixed(CheckerContext *ctx, Ast *array_typ_expr, Ast *elem_expr, Type *elem, i64 count, Type *generic_type); gb_internal Type *make_soa_struct_slice(CheckerContext *ctx, Ast *array_typ_expr, Ast *elem_expr, Type *elem); gb_internal Type *make_soa_struct_dynamic_array(CheckerContext *ctx, Ast *array_typ_expr, Ast *elem_expr, Type *elem); @@ -124,6 +125,8 @@ gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types gb_internal bool complete_soa_type(Checker *checker, Type *t, bool wait_to_finish); +gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y); + enum LoadDirectiveResult { LoadDirective_Success = 0, LoadDirective_Error = 1, @@ -278,8 +281,20 @@ gb_internal void error_operand_not_expression(Operand *o) { gb_internal void error_operand_no_value(Operand *o) { if (o->mode == Addressing_NoValue) { - gbString err = expr_to_string(o->expr); Ast *x = unparen_expr(o->expr); + + if (x->kind == Ast_CallExpr) { + Ast *p = unparen_expr(x->CallExpr.proc); + if (p->kind == Ast_BasicDirective) { + String tag = p->BasicDirective.name.string; + if (tag == "panic" || + tag == "assert") { + return; + } + } + } + + gbString err = expr_to_string(o->expr); if (x->kind == Ast_CallExpr) { error(o->expr, "'%s' call does not return a value and cannot be used as a value", err); } else { @@ -485,7 +500,9 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E nctx.no_polymorphic_errors = false; // NOTE(bill): Reset scope from the failed procedure type - scope_reset(scope); + scope->head_child.store(nullptr, std::memory_order_relaxed); + string_map_clear(&scope->elements); + ptr_set_clear(&scope->imported); // LEAK NOTE(bill): Cloning this AST may be leaky but this is not really an issue due to arena-based allocation Ast *cloned_proc_type_node = clone_ast(pt->node); @@ -533,13 +550,15 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E final_proc_type->Proc.is_poly_specialized = true; final_proc_type->Proc.is_polymorphic = true; - final_proc_type->Proc.variadic = src->Proc.variadic; - final_proc_type->Proc.require_results = src->Proc.require_results; - final_proc_type->Proc.c_vararg = src->Proc.c_vararg; - final_proc_type->Proc.has_named_results = src->Proc.has_named_results; - final_proc_type->Proc.diverging = src->Proc.diverging; - final_proc_type->Proc.return_by_pointer = src->Proc.return_by_pointer; - final_proc_type->Proc.optional_ok = src->Proc.optional_ok; + final_proc_type->Proc.variadic = src->Proc.variadic; + final_proc_type->Proc.require_results = src->Proc.require_results; + final_proc_type->Proc.c_vararg = src->Proc.c_vararg; + final_proc_type->Proc.has_named_results = src->Proc.has_named_results; + final_proc_type->Proc.diverging = src->Proc.diverging; + final_proc_type->Proc.return_by_pointer = src->Proc.return_by_pointer; + final_proc_type->Proc.optional_ok = src->Proc.optional_ok; + final_proc_type->Proc.enable_target_feature = src->Proc.enable_target_feature; + final_proc_type->Proc.require_target_feature = src->Proc.require_target_feature; for (isize i = 0; i < operands.count; i++) { @@ -563,6 +582,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E d->defer_use_checked = false; Entity *entity = alloc_entity_procedure(nullptr, token, final_proc_type, tags); + entity->state.store(EntityState_Resolved); entity->identifier = ident; add_entity_and_decl_info(&nctx, ident, entity, d); @@ -571,6 +591,16 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E entity->file = base_entity->file; entity->pkg = base_entity->pkg; entity->flags = 0; + + entity->Procedure.optimization_mode = base_entity->Procedure.optimization_mode; + + if (base_entity->flags & EntityFlag_Cold) { + entity->flags |= EntityFlag_Cold; + } + if (base_entity->flags & EntityFlag_Disabled) { + entity->flags |= EntityFlag_Disabled; + } + d->entity = entity; AstFile *file = nullptr; @@ -1178,15 +1208,59 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ LIT(context_name)); check_assignment_error_suggestion(c, operand, type); + Type *src = base_type(operand->type); + Type *dst = base_type(type); if (context_name == "procedure argument") { - Type *src = base_type(operand->type); - Type *dst = base_type(type); if (is_type_slice(src) && are_types_identical(src->Slice.elem, dst)) { gbString a = expr_to_string(operand->expr); error_line("\tSuggestion: Did you mean to pass the slice into the variadic parameter with ..%s?\n\n", a); gb_string_free(a); } } + if (src->kind == dst->kind && src->kind == Type_Proc) { + Type *x = src; + Type *y = dst; + bool same_inputs = are_types_identical_internal(x->Proc.params, y->Proc.params, false); + bool same_outputs = are_types_identical_internal(x->Proc.results, y->Proc.results, false); + if (same_inputs && same_outputs && + x->Proc.calling_convention != y->Proc.calling_convention) { + gbString s_expected = type_to_string(y); + gbString s_got = type_to_string(x); + + error_line("\tNote: The calling conventions differ between the procedure signature types\n"); + error_line("\t Expected \"%s\", got \"%s\"\n", + proc_calling_convention_strings[y->Proc.calling_convention], + proc_calling_convention_strings[x->Proc.calling_convention]); + 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); + error_line("\tNote: The return types differ between the procedure signature types\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.params); + gbString s_got = type_to_string(x->Proc.params); + error_line("\tNote: The input parameter types differ between the procedure signature types\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 { + gbString s_expected = type_to_string(y); + gbString s_got = type_to_string(x); + error_line("\tNote: The signature type do not match whatsoever\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); + } + } } break; } @@ -1375,6 +1449,16 @@ gb_internal bool is_polymorphic_type_assignable(CheckerContext *c, Type *poly, T if (!is_polymorphic_type_assignable(c, poly->BitSet.elem, source->BitSet.elem, true, modify_type)) { return false; } + + // For generic types like bit_set[$T] the upper and lower of the poly type will be zeroes since + // it could not figure that stuff out when the poly type was created. + if (poly->BitSet.upper == 0 && modify_type) { + poly->BitSet.upper = source->BitSet.upper; + } + if (poly->BitSet.lower == 0 && modify_type) { + poly->BitSet.lower = source->BitSet.lower; + } + if (poly->BitSet.underlying == nullptr) { if (modify_type) { poly->BitSet.underlying = source->BitSet.underlying; @@ -1409,11 +1493,16 @@ gb_internal bool is_polymorphic_type_assignable(CheckerContext *c, Type *poly, T poly->Struct.soa_kind != StructSoa_None) { bool ok = is_polymorphic_type_assignable(c, poly->Struct.soa_elem, source->Struct.soa_elem, true, modify_type); if (ok) switch (source->Struct.soa_kind) { - case StructSoa_Fixed: + case StructSoa_None: default: GB_PANIC("Unhandled SOA Kind"); break; - + case StructSoa_Fixed: + if (modify_type) { + Type *type = make_soa_struct_fixed(c, nullptr, poly->Struct.node, poly->Struct.soa_elem, poly->Struct.soa_count, nullptr); + gb_memmove(poly, type, gb_size_of(*type)); + } + break; case StructSoa_Slice: if (modify_type) { Type *type = make_soa_struct_slice(c, nullptr, poly->Struct.node, poly->Struct.soa_elem); @@ -1435,6 +1524,13 @@ gb_internal bool is_polymorphic_type_assignable(CheckerContext *c, Type *poly, T // return check_is_assignable_to(c, &o, poly); // && is_type_subtype_of_and_allow_polymorphic(o.type, poly); } return false; + + case Type_BitField: + if (source->kind == Type_BitField) { + return is_polymorphic_type_assignable(c, poly->BitField.backing_type, source->BitField.backing_type, true, modify_type); + } + return false; + case Type_Tuple: GB_PANIC("This should never happen"); return false; @@ -1748,7 +1844,7 @@ gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *nam case Entity_ImportName: if (!allow_import_name) { - error(n, "Use of import '%.*s' not in selector", LIT(name)); + error(n, "Use of import name '%.*s' not in the form of 'x.y'", LIT(name)); } return e; case Entity_LibraryName: @@ -1781,6 +1877,13 @@ gb_internal bool check_unary_op(CheckerContext *c, Operand *o, Token op) { gb_string_free(str); return false; } + if (o->mode == Addressing_Type) { + gbString str = type_to_string(o->type); + error(o->expr, "Expected an expression for operator '%.*s', got type '%s'", LIT(op.string), str); + gb_string_free(str); + return false; + } + Type *type = base_type(core_array_type(o->type)); gbString str = nullptr; switch (op.kind) { @@ -1802,11 +1905,13 @@ gb_internal bool check_unary_op(CheckerContext *c, Operand *o, Token op) { case Token_Not: if (!is_type_boolean(type) || is_type_array_like(o->type)) { ERROR_BLOCK(); - str = expr_to_string(o->expr); error(op, "Operator '%.*s' is only allowed on boolean expressions", LIT(op.string)); - gb_string_free(str); if (is_type_integer(type)) { - error_line("\tSuggestion: Did you mean to use the bitwise not operator '~'?\n"); + str = expr_to_string(o->expr); + error_line("\tSuggestion: Did you mean to do one of the following?\n"); + error_line("\t\t'%s == 0'?\n", str); + error_line("\t\tUse of the bitwise not operator '~'?\n"); + gb_string_free(str); } } else { o->type = t_untyped_bool; @@ -2186,6 +2291,17 @@ gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue i gb_internal bool check_integer_exceed_suggestion(CheckerContext *c, Operand *o, Type *type, i64 max_bit_size=0) { if (is_type_integer(type) && o->value.kind == ExactValue_Integer) { gbString b = type_to_string(type); + defer (gb_string_free(b)); + + if (is_type_enum(o->type)) { + if (check_is_castable_to(c, o, type)) { + gbString ot = type_to_string(o->type); + error_line("\tSuggestion: Try casting the '%s' expression to '%s'", ot, b); + gb_string_free(ot); + } + return true; + } + i64 sz = type_size_of(type); i64 bit_size = 8*sz; @@ -2235,7 +2351,6 @@ gb_internal bool check_integer_exceed_suggestion(CheckerContext *c, Operand *o, } } - gb_string_free(b); return true; } @@ -2418,32 +2533,6 @@ gb_internal bool check_is_not_addressable(CheckerContext *c, Operand *o) { return o->mode != Addressing_Variable && o->mode != Addressing_SoaVariable; } -gb_internal void check_old_for_or_switch_value_usage(Ast *expr) { - Entity *e = entity_of_node(expr); - if (e != nullptr && (e->flags & EntityFlag_OldForOrSwitchValue) != 0) { - GB_ASSERT(e->kind == Entity_Variable); - - ERROR_BLOCK(); - - if ((e->flags & EntityFlag_ForValue) != 0) { - Type *parent_type = type_deref(e->Variable.for_loop_parent_type); - - error(expr, "Assuming a for-in defined value is addressable as the iterable is passed by value has been disallowed."); - - if (is_type_map(parent_type)) { - error_line("\tSuggestion: Prefer doing 'for key, &%.*s in ...'\n", LIT(e->token.string)); - } else { - error_line("\tSuggestion: Prefer doing 'for &%.*s in ...'\n", LIT(e->token.string)); - } - } else { - GB_ASSERT((e->flags & EntityFlag_SwitchValue) != 0); - - error(expr, "Assuming a switch-in defined value is addressable as the iterable is passed by value has been disallowed."); - error_line("\tSuggestion: Prefer doing 'switch &%.*s in ...'\n", LIT(e->token.string)); - } - } -} - gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast *node) { switch (op.kind) { case Token_And: { // Pointer address @@ -2471,7 +2560,10 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * { ERROR_BLOCK(); error(op, "Cannot take the pointer address of '%s'", str); - if (e != nullptr && (e->flags & EntityFlag_ForValue) != 0) { + if (e == nullptr) { + break; + } + if ((e->flags & EntityFlag_ForValue) != 0) { Type *parent_type = type_deref(e->Variable.for_loop_parent_type); if (parent_type != nullptr && is_type_string(parent_type)) { @@ -2481,9 +2573,17 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * } else { error_line("\tSuggestion: Did you want to pass the iterable value to the for statement by pointer to get addressable semantics?\n"); } + + if (parent_type != nullptr && is_type_map(parent_type)) { + error_line("\t Prefer doing 'for key, &%.*s in ...'\n", LIT(e->token.string)); + } else { + error_line("\t Prefer doing 'for &%.*s in ...'\n", LIT(e->token.string)); + } } - if (e != nullptr && (e->flags & EntityFlag_SwitchValue) != 0) { + if ((e->flags & EntityFlag_SwitchValue) != 0) { error_line("\tSuggestion: Did you want to pass the value to the switch statement by pointer to get addressable semantics?\n"); + + error_line("\t Prefer doing 'switch &%.*s in ...'\n", LIT(e->token.string)); } } break; @@ -2505,11 +2605,6 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * o->type = alloc_type_pointer(o->type); } } else { - if (ast_node_expect(node, Ast_UnaryExpr)) { - ast_node(ue, UnaryExpr, node); - check_old_for_or_switch_value_usage(ue->expr); - } - o->type = alloc_type_pointer(o->type); } @@ -2535,6 +2630,11 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * if (o->mode == Addressing_Constant) { Type *type = base_type(o->type); if (!is_type_constant_type(o->type)) { + if (is_type_array_like(o->type)) { + o->mode = Addressing_Value; + return; + } + gbString xt = type_to_string(o->type); gbString err_str = expr_to_string(node); error(op, "Invalid type, '%s', for constant unary expression '%s'", xt, err_str); @@ -3234,7 +3334,7 @@ gb_internal bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) } -gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type) { +gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type, bool forbid_identical = false) { if (!is_operand_value(*x)) { error(x->expr, "Only values can be casted"); x->mode = Addressing_Invalid; @@ -3280,6 +3380,9 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type) { if (is_type_untyped(x->type)) { Type *final_type = type; if (is_const_expr && !is_type_constant_type(type)) { + if (is_type_union(type)) { + convert_to_typed(c, x, type); + } final_type = default_type(x->type); } update_untyped_expr_type(c, x->expr, final_type, true); @@ -3303,27 +3406,37 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type) { add_package_dependency(c, "runtime", "gnu_f2h_ieee", REQUIRE); } } + // If we check polymorphic procedures, we risk erring on + // identical casts that cannot be foreseen or otherwise + // forbidden, so just skip them. + if (forbid_identical && check_vet_flags(c) & VetFlag_Cast && + (c->curr_proc_sig == nullptr || !is_type_polymorphic(c->curr_proc_sig))) { + Type *src_exact = x->type; + Type *dst_exact = type; + + if (src_exact != nullptr && + dst_exact != nullptr && + are_types_identical(src_exact, dst_exact) + ) { + gbString oper_str = expr_to_string(x->expr); + gbString to_type = type_to_string(dst_exact); + error(x->expr, "Unneeded cast of '%s' to identical type '%s'", oper_str, to_type); + gb_string_free(oper_str); + gb_string_free(to_type); + } + } } x->type = type; } -gb_internal bool check_transmute(CheckerContext *c, Ast *node, Operand *o, Type *t) { +gb_internal bool check_transmute(CheckerContext *c, Ast *node, Operand *o, Type *t, bool forbid_identical = false) { if (!is_operand_value(*o)) { error(o->expr, "'transmute' can only be applied to values"); o->mode = Addressing_Invalid; return false; } - // if (o->mode == Addressing_Constant) { - // gbString expr_str = expr_to_string(o->expr); - // error(o->expr, "Cannot transmute a constant expression: '%s'", expr_str); - // gb_string_free(expr_str); - // o->mode = Addressing_Invalid; - // o->expr = node; - // return false; - // } - Type *src_t = o->type; Type *dst_t = t; Type *src_bt = base_type(src_t); @@ -3406,6 +3519,36 @@ gb_internal bool check_transmute(CheckerContext *c, Ast *node, Operand *o, Type return true; } } + } else { + // If we check polymorphic procedures, we risk erring on + // identical casts that cannot be foreseen or otherwise + // forbidden, so just skip them. + if (forbid_identical && check_vet_flags(c) & VetFlag_Cast && + (c->curr_proc_sig == nullptr || !is_type_polymorphic(c->curr_proc_sig))) { + if (are_types_identical(src_t, dst_t)) { + gbString oper_str = expr_to_string(o->expr); + gbString to_type = type_to_string(dst_t); + error(o->expr, "Unneeded transmute of '%s' to identical type '%s'", oper_str, to_type); + gb_string_free(oper_str); + gb_string_free(to_type); + } else if (is_type_internally_pointer_like(src_t) && + is_type_internally_pointer_like(dst_t)) { + error(o->expr, "Use of 'transmute' where 'cast' would be preferred since the types are pointer-like"); + } else if (are_types_identical(src_bt, dst_bt)) { + gbString oper_str = expr_to_string(o->expr); + gbString to_type = type_to_string(dst_t); + error(o->expr, "Unneeded transmute of '%s' to identical type '%s'", oper_str, to_type); + gb_string_free(oper_str); + gb_string_free(to_type); + } else if (is_type_integer(src_t) && is_type_integer(dst_t) && + types_have_same_internal_endian(src_t, dst_t)) { + gbString oper_type = type_to_string(src_t); + gbString to_type = type_to_string(dst_t); + error(o->expr, "Use of 'transmute' where 'cast' would be preferred since both are integers of the same endianness, from '%s' to '%s'", oper_type, to_type); + gb_string_free(to_type); + gb_string_free(oper_type); + } + } } o->mode = Addressing_Value; @@ -3490,6 +3633,9 @@ gb_internal void check_binary_matrix(CheckerContext *c, Token const &op, Operand x->mode = Addressing_Value; if (are_types_identical(xt, yt)) { + if (are_types_identical(x->type, y->type)) { + return; + } if (!is_type_named(x->type) && is_type_named(y->type)) { // prefer the named type x->type = y->type; @@ -4228,7 +4374,8 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar } else { switch (operand->type->Basic.kind) { case Basic_UntypedBool: - if (!is_type_boolean(target_type)) { + if (!is_type_boolean(target_type) && + !is_type_integer(target_type)) { operand->mode = Addressing_Invalid; convert_untyped_error(c, operand, target_type); return; @@ -4348,9 +4495,14 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar defer (gb_string_free(type_str)); if (valid_count == 1) { + Type *new_type = t->Union.variants[first_success_index]; + target_type = new_type; + if (is_type_union(new_type)) { + convert_to_typed(c, operand, new_type); + break; + } + operand->type = new_type; operand->mode = Addressing_Value; - operand->type = t->Union.variants[first_success_index]; - target_type = t->Union.variants[first_success_index]; break; } else if (valid_count > 1) { ERROR_BLOCK(); @@ -4830,7 +4982,27 @@ gb_internal bool is_entity_declared_for_selector(Entity *entity, Scope *import_s // NOTE(bill, 2022-02-03): see `check_const_decl` for why it exists reasoning gb_internal Entity *check_entity_from_ident_or_selector(CheckerContext *c, Ast *node, bool ident_only) { - if (node->kind == Ast_Ident) { + if (node == nullptr) { + return nullptr; + } + /*if (node->kind == Ast_TernaryWhenExpr) { + ast_node(we, TernaryWhenExpr, node); + if (we->cond == nullptr) { + return nullptr; + } + if (we->cond->tav.mode != Addressing_Constant) { + return nullptr; + } + if (we->cond->tav.value.kind != ExactValue_Bool) { + return nullptr; + } + if (we->cond->tav.value.value_bool) { + return check_entity_from_ident_or_selector(c, we->x, ident_only); + } else { + Entity *e = check_entity_from_ident_or_selector(c, we->y, ident_only); + return e; + } + } else */if (node->kind == Ast_Ident) { String name = node->Ident.token.string; return scope_lookup(c->scope, name); } else if (!ident_only) if (node->kind == Ast_SelectorExpr) { @@ -5863,6 +6035,22 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A Entity *vt = pt->params->Tuple.variables[pt->variadic_index]; o.type = vt->type; + + // NOTE(bill, 2024-07-14): minimize the stack usage for variadic parameters with the backing array + if (c->decl) { + bool found = false; + for (auto &vr : c->decl->variadic_reuses) { + if (are_types_identical(vt->type, vr.slice_type)) { + vr.max_count = gb_max(vr.max_count, variadic_operands.count); + found = true; + break; + } + } + if (!found) { + array_add(&c->decl->variadic_reuses, VariadicReuseData{vt->type, variadic_operands.count}); + } + } + } else { dummy_argument_count += 1; o.type = t_untyped_nil; @@ -6030,7 +6218,9 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A Type *t = elem; if (is_type_polymorphic(t)) { - error(call, "Ambiguous call to a polymorphic variadic procedure with no variadic input %s", type_to_string(final_proc_type)); + if (show_error) { + error(call, "Ambiguous call to a polymorphic variadic procedure with no variadic input %s", type_to_string(final_proc_type)); + } err = CallArgumentError_AmbiguousPolymorphicVariadic; } @@ -6185,6 +6375,20 @@ gb_internal bool evaluate_where_clauses(CheckerContext *ctx, Ast *call_expr, Sco } return false; } + + if (ast_file_vet_style(ctx->file)) { + Ast *c = unparen_expr(clause); + if (c->kind == Ast_BinaryExpr && c->BinaryExpr.op.kind == Token_CmpAnd) { + ERROR_BLOCK(); + error(c, "Prefer to separate 'where' clauses with a comma rather than '&&'"); + gbString x = expr_to_string(c->BinaryExpr.left); + gbString y = expr_to_string(c->BinaryExpr.right); + error_line("\tSuggestion: '%s, %s'\n", x, y); + gb_string_free(y); + gb_string_free(x); + } + } + } } @@ -6526,12 +6730,17 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, array_add(&proc_entities, proc); } + int max_matched_features = 0; gbString expr_name = expr_to_string(operand->expr); defer (gb_string_free(expr_name)); for_array(i, procs) { Entity *p = procs[i]; + if (p->flags & EntityFlag_Disabled) { + continue; + } + Type *pt = base_type(p->type); if (pt != nullptr && is_type_proc(pt)) { CallArgumentData data = {}; @@ -6562,11 +6771,24 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, item.score += assign_score_function(1); } + max_matched_features = gb_max(max_matched_features, matched_target_features(&pt->Proc)); + item.index = index; array_add(&valids, item); } } + if (max_matched_features > 0) { + for_array(i, valids) { + Entity *p = procs[valids[i].index]; + Type *t = base_type(p->type); + GB_ASSERT(t->kind == Type_Proc); + + int matched = matched_target_features(&t->Proc); + valids[i].score += assign_score_function(max_matched_features-matched); + } + } + if (valids.count > 1) { array_sort(valids, valid_index_and_score_cmp); i64 best_score = valids[0].score; @@ -6630,9 +6852,73 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, if (procs.count > 0) { error_line("Did you mean to use one of the following:\n"); } + + // Try to reduce the list further for `$T: typeid` like parameters + bool *possibly_ignore = gb_alloc_array(temporary_allocator(), bool, procs.count); + isize possibly_ignore_set = 0; + + if (true) { + // NOTE(bill): This currently only checks for #soa types + for_array(i, procs) { + Entity *proc = procs[i]; + Type *t = base_type(proc->type); + if (t == nullptr || t->kind != Type_Proc) { + continue; + } + + TypeProc *pt = &t->Proc; + if (pt->param_count == 0) { + continue; + } + + for_array(j, pt->params->Tuple.variables) { + Entity *v = pt->params->Tuple.variables[j]; + if (v->kind != Entity_TypeName) { + continue; + } + + Type *dst_t = base_type(v->type); + while (dst_t->kind == Type_Generic && dst_t->Generic.specialized) { + dst_t = dst_t->Generic.specialized; + } + + if (j >= positional_operands.count) { + continue; + } + Operand const &o = positional_operands[j]; + if (o.mode != Addressing_Type) { + continue; + } + Type *t = base_type(o.type); + if (t->kind == dst_t->kind) { + continue; + } + Type *st = base_type(type_deref(o.type)); + Type *dt = base_type(type_deref(dst_t)); + if (st->kind == dt->kind) { + continue; + } + if (is_type_soa_struct(st)) { + possibly_ignore[i] = true; + possibly_ignore_set += 1; + continue; + } + } + } + } + + if (possibly_ignore_set == procs.count) { + possibly_ignore_set = 0; + } + + isize max_name_length = 0; isize max_type_length = 0; - for (Entity *proc : procs) { + for_array(i, procs) { + if (possibly_ignore_set != 0 && possibly_ignore[i]) { + continue; + } + Entity *proc = procs[i]; Type *t = base_type(proc->type); if (t == t_invalid) continue; String prefix = {}; @@ -6662,7 +6948,11 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, } spaces[max_spaces] = 0; - for (Entity *proc : procs) { + for_array(i, procs) { + if (possibly_ignore_set != 0 && possibly_ignore[i]) { + continue; + } + Entity *proc = procs[i]; TokenPos pos = proc->token.pos; Type *t = base_type(proc->type); if (t == t_invalid) continue; @@ -6708,7 +6998,11 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, ERROR_BLOCK(); error(operand->expr, "Ambiguous procedure group call '%s' that match with the given arguments", expr_name); - print_argument_types(); + if (positional_operands.count == 0 && named_operands.count == 0) { + error_line("\tNo given arguments\n"); + } else { + print_argument_types(); + } for (auto const &valid : valids) { Entity *proc = proc_entities[valid.index]; @@ -6921,6 +7215,8 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O Array operands = {}; defer (array_free(&operands)); + CallArgumentError err = CallArgumentError_None; + bool named_fields = false; { // NOTE(bill, 2019-10-26): Allow a cycle in the parameters but not in the fields themselves @@ -6938,6 +7234,11 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O Ast *arg = ce->args[i]; ast_node(fv, FieldValue, arg); + if (fv->value == nullptr) { + error(fv->eq, "Expected a value"); + err = CallArgumentError_InvalidFieldValue; + continue; + } if (fv->field->kind == Ast_Ident) { String name = fv->field->Ident.token.string; isize index = lookup_polymorphic_record_parameter(original_type, name); @@ -6976,7 +7277,10 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O } - CallArgumentError err = CallArgumentError_None; + if (err != 0) { + operand->mode = Addressing_Invalid; + return err; + } TypeTuple *tuple = get_record_polymorphic_params(original_type); isize param_count = tuple->variables.count; @@ -7237,14 +7541,9 @@ 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)); - Type *params = nullptr; - switch (bt->kind) { - case Type_Struct: params = bt->Struct.polymorphic_params; break; - case Type_Union: params = bt->Union.polymorphic_params; break; - } - - if (params != nullptr) for_array(i, params->Tuple.variables) { - Entity *v = params->Tuple.variables[i]; + TypeTuple *tuple = get_record_polymorphic_params(e->type); + if (tuple != nullptr) for_array(i, tuple->variables) { + Entity *v = tuple->variables[i]; String name = v->token.string; if (i > 0) { s = gb_string_append_fmt(s, ", "); @@ -7326,13 +7625,15 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c String name = bd->name.string; if ( name == "location" || + name == "exists" || name == "assert" || name == "panic" || name == "defined" || name == "config" || name == "load" || name == "load_directory" || - name == "load_hash" + name == "load_hash" || + name == "hash" ) { operand->mode = Addressing_Builtin; operand->builtin_id = BuiltinProc_DIRECTIVE; @@ -7553,8 +7854,11 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } } + bool is_call_inlined = false; + switch (inlining) { case ProcInlining_inline: + is_call_inlined = true; if (proc != nullptr) { Entity *e = entity_from_expr(proc); if (e != nullptr && e->kind == Entity_Procedure) { @@ -7562,7 +7866,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c if (decl->proc_lit) { ast_node(pl, ProcLit, decl->proc_lit); if (pl->inlining == ProcInlining_no_inline) { - error(call, "'#force_inline' cannot be applied to a procedure that has be marked as '#force_no_inline'"); + error(call, "'#force_inline' cannot be applied to a procedure that has been marked as '#force_no_inline'"); } } } @@ -7570,6 +7874,50 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c break; case ProcInlining_no_inline: break; + case ProcInlining_none: + if (proc != nullptr) { + Entity *e = entity_from_expr(proc); + if (e != nullptr && e->kind == Entity_Procedure) { + DeclInfo *decl = e->decl_info; + if (decl->proc_lit) { + ast_node(pl, ProcLit, decl->proc_lit); + if (pl->inlining == ProcInlining_inline) { + is_call_inlined = true; + } + } + } + } + } + + { + String invalid; + if (pt->kind == Type_Proc && pt->Proc.require_target_feature.len != 0) { + if (!check_target_feature_is_valid_for_target_arch(pt->Proc.require_target_feature, &invalid)) { + error(call, "Called procedure requires target feature '%.*s' which is invalid for the build target", LIT(invalid)); + } else if (!check_target_feature_is_enabled(pt->Proc.require_target_feature, &invalid)) { + error(call, "Calling this procedure requires target feature '%.*s' to be enabled", LIT(invalid)); + } + } + + if (pt->kind == Type_Proc && pt->Proc.enable_target_feature.len != 0) { + if (!check_target_feature_is_valid_for_target_arch(pt->Proc.enable_target_feature, &invalid)) { + error(call, "Called procedure enables target feature '%.*s' which is invalid for the build target", LIT(invalid)); + } + + // NOTE: Due to restrictions in LLVM you can not inline calls with a superset of features. + if (is_call_inlined) { + if (c->curr_proc_decl == nullptr) { + error(call, "Calling a '#force_inline' procedure that enables target features is not allowed at file scope"); + } else { + GB_ASSERT(c->curr_proc_decl->entity); + GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc); + String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature; + if (!check_target_feature_is_superset_of(scope_features, pt->Proc.enable_target_feature, &invalid)) { + error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid)); + } + } + } + } } operand->expr = call; @@ -7673,13 +8021,18 @@ gb_internal bool check_set_index_data(Operand *o, Type *t, bool indirection, i64 return true; case Type_Matrix: - *max_count = t->Matrix.column_count; if (indirection) { o->mode = Addressing_Variable; } else if (o->mode != Addressing_Variable) { o->mode = Addressing_Value; } - o->type = alloc_type_array(t->Matrix.elem, t->Matrix.row_count); + if (t->Matrix.is_row_major) { + *max_count = t->Matrix.row_count; + o->type = alloc_type_array(t->Matrix.elem, t->Matrix.column_count); + } else { + *max_count = t->Matrix.column_count; + o->type = alloc_type_array(t->Matrix.elem, t->Matrix.row_count); + } return true; case Type_Slice: @@ -7724,8 +8077,8 @@ gb_internal bool check_set_index_data(Operand *o, Type *t, bool indirection, i64 if (is_type_pointer(original_type) && indirection) { Type *ptr = base_type(original_type); - if (ptr->kind == Type_Pointer && o->mode == Addressing_SoaVariable) { - o->type = ptr->Pointer.elem; + if (ptr->kind == Type_MultiPointer && o->mode == Addressing_SoaVariable) { + o->type = ptr->MultiPointer.elem; o->mode = Addressing_Value; return true; } @@ -8079,11 +8432,10 @@ gb_internal void add_constant_switch_case(CheckerContext *ctx, SeenMap *seen, Op } uintptr key = hash_exact_value(operand.value); - TypeAndToken *found = map_get(seen, key); - if (found != nullptr) { + GB_ASSERT(key != 0); + isize count = multi_map_count(seen, key); + if (count) { TEMPORARY_ALLOCATOR_GUARD(); - - isize count = multi_map_count(seen, key); TypeAndToken *taps = gb_alloc_array(temporary_allocator(), TypeAndToken, count); multi_map_get_all(seen, key, taps); @@ -8163,6 +8515,14 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A } o->type = t_untyped_string; o->value = exact_value_string(file); + } else if (name == "directory") { + String file = get_file_path_string(bd->token.pos.file_id); + String path = dir_from_path(file); + if (build_context.obfuscate_source_code_locations) { + path = obfuscate_string(path, "D"); + } + o->type = t_untyped_string; + o->value = exact_value_string(path); } else if (name == "line") { i32 line = bd->token.pos.line; if (build_context.obfuscate_source_code_locations) { @@ -8198,6 +8558,7 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A name == "assert" || name == "defined" || name == "config" || + name == "exists" || name == "load" || name == "load_hash" || name == "load_directory" || @@ -8554,7 +8915,7 @@ gb_internal ExprKind check_or_branch_expr(CheckerContext *c, Operand *o, Ast *no // okay } else { gbString s = type_to_string(right_type); - error(node, "'%.*s' requires a boolean or nil-able type, got %s", s); + error(node, "'%.*s' requires a boolean or nil-able type, got %s", LIT(name), s); gb_string_free(s); } } @@ -8721,6 +9082,10 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, SliceArray.elem; break; + case Type_BitField: + is_constant = false; + ft = bt->BitField.fields[index]->type; + break; default: GB_PANIC("invalid type: %s", type_to_string(ft)); break; @@ -8747,6 +9112,9 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, SliceArray.elem; break; + case Type_BitField: + nested_ft = bt->BitField.fields[index]->type; + break; default: GB_PANIC("invalid type %s", type_to_string(nested_ft)); break; @@ -8820,12 +9188,16 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * type = nullptr; // [?]Type - if (type_expr->kind == Ast_ArrayType && type_expr->ArrayType.count != nullptr) { + if (type_expr->kind == Ast_ArrayType) { Ast *count = type_expr->ArrayType.count; - if (count->kind == Ast_UnaryExpr && - count->UnaryExpr.op.kind == Token_Question) { - type = alloc_type_array(check_type(c, type_expr->ArrayType.elem), -1); - is_to_be_determined_array_count = true; + if (count != nullptr) { + if (count->kind == Ast_UnaryExpr && + count->UnaryExpr.op.kind == Token_Question) { + type = alloc_type_array(check_type(c, type_expr->ArrayType.elem), -1); + is_to_be_determined_array_count = true; + } + } else { + type = alloc_type_slice(check_type(c, type_expr->ArrayType.elem)); } if (cl->elems.count > 0) { if (type_expr->ArrayType.tag != nullptr) { @@ -8838,8 +9210,7 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * } } } - } - if (type_expr->kind == Ast_DynamicArrayType && type_expr->DynamicArrayType.tag != nullptr) { + } else if (type_expr->kind == Ast_DynamicArrayType && type_expr->DynamicArrayType.tag != nullptr) { if (cl->elems.count > 0) { Ast *tag = type_expr->DynamicArrayType.tag; GB_ASSERT(tag->kind == Ast_BasicDirective); @@ -8878,6 +9249,12 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * if (cl->elems.count == 0) { break; // NOTE(bill): No need to init } + + if (t->Struct.soa_kind != StructSoa_None) { + error(node, "#soa arrays are not supported for compound literals"); + break; + } + if (t->Struct.is_raw_union) { if (cl->elems.count > 0) { // NOTE: unions cannot be constant @@ -9406,7 +9783,8 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * continue; } ExactValue v = f->Constant.value; - auto found = map_get(&seen, hash_exact_value(v)); + uintptr hash = hash_exact_value(v); + auto found = map_get(&seen, hash); if (!found) { array_add(&unhandled, f); } @@ -9569,10 +9947,14 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * } Type *et = base_type(t->BitSet.elem); isize field_count = 0; - if (et->kind == Type_Enum) { + if (et != nullptr && et->kind == Type_Enum) { field_count = et->Enum.fields.count; } + if (is_type_array(bit_set_to_int(t))) { + is_constant = false; + } + if (cl->elems[0]->kind == Ast_FieldValue) { error(cl->elems[0], "'field = value' in a bit_set a literal is not allowed"); is_constant = false; @@ -9651,7 +10033,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * if (tav.mode != Addressing_Constant) { continue; } - GB_ASSERT(tav.value.kind == ExactValue_Integer); + if (tav.value.kind != ExactValue_Integer) { + continue; + } i64 v = big_int_to_i64(&tav.value.value_integer); i64 lower = bt->BitSet.lower; u64 index = cast(u64)(v-lower); @@ -10075,7 +10459,7 @@ gb_internal ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node, o->mode = Addressing_Invalid; o->expr = node; return kind; - } else if (ok) { + } else if (ok && !is_type_matrix(t)) { ExactValue value = type_and_value_of_expr(ie->expr).value; o->mode = Addressing_Constant; bool success = false; @@ -10161,6 +10545,17 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, case Type_Struct: if (is_type_soa_struct(t)) { valid = true; + if (t->Struct.soa_kind == StructSoa_Fixed) { + max_count = t->Struct.soa_count; + if (o->mode != Addressing_Variable && !is_type_pointer(o->type)) { + gbString str = expr_to_string(node); + error(node, "Cannot slice #soa array '%s', value is not addressable", str); + gb_string_free(str); + o->mode = Addressing_Invalid; + o->expr = node; + return kind; + } + } o->type = make_soa_struct_slice(c, nullptr, nullptr, t->Struct.soa_elem); } break; @@ -10516,10 +10911,10 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast if (o->mode != Addressing_Invalid) { switch (tc->token.kind) { case Token_transmute: - check_transmute(c, node, o, type); + check_transmute(c, node, o, type, true); break; case Token_cast: - check_cast(c, o, type); + check_cast(c, o, type, true); break; default: error(node, "Invalid AST: Invalid casting expression"); @@ -10700,6 +11095,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast return Expr_Expr; case_end; + case Ast_DistinctType: case Ast_TypeidType: case Ast_PolyType: case Ast_ProcType: @@ -11164,6 +11560,9 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan case_end; case_ast_node(pt, PointerType, node); + if (pt->tag) { + str = write_expr_to_string(str, pt->tag, false); + } str = gb_string_append_rune(str, '^'); str = write_expr_to_string(str, pt->type, shorthand); case_end; @@ -11174,6 +11573,9 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan case_end; case_ast_node(at, ArrayType, node); + if (at->tag) { + str = write_expr_to_string(str, at->tag, false); + } str = gb_string_append_rune(str, '['); if (at->count != nullptr && at->count->kind == Ast_UnaryExpr && @@ -11187,6 +11589,9 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan case_end; case_ast_node(at, DynamicArrayType, node); + if (at->tag) { + str = write_expr_to_string(str, at->tag, false); + } str = gb_string_appendc(str, "[dynamic]"); str = write_expr_to_string(str, at->elem, shorthand); case_end; diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 971841165..74397828d 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -161,8 +161,7 @@ gb_internal bool check_is_terminating_list(Slice const &stmts, String con } gb_internal bool check_has_break_list(Slice const &stmts, String const &label, bool implicit) { - for_array(i, stmts) { - Ast *stmt = stmts[i]; + for (Ast *stmt : stmts) { if (check_has_break(stmt, label, implicit)) { return true; } @@ -170,6 +169,21 @@ gb_internal bool check_has_break_list(Slice const &stmts, String const &l return false; } +gb_internal bool check_has_break_expr(Ast * expr, String const &label) { + if (expr && expr->viral_state_flags & ViralStateFlag_ContainsOrBreak) { + return true; + } + return false; +} + +gb_internal bool check_has_break_expr_list(Slice const &exprs, String const &label) { + for (Ast *expr : exprs) { + if (check_has_break_expr(expr, label)) { + return true; + } + } + return false; +} gb_internal bool check_has_break(Ast *stmt, String const &label, bool implicit) { switch (stmt->kind) { @@ -189,6 +203,13 @@ gb_internal bool check_has_break(Ast *stmt, String const &label, bool implicit) return check_has_break_list(stmt->BlockStmt.stmts, label, implicit); case Ast_IfStmt: + if (stmt->IfStmt.init && check_has_break(stmt->IfStmt.init, label, implicit)) { + return true; + } + if (stmt->IfStmt.cond && check_has_break_expr(stmt->IfStmt.cond, label)) { + return true; + } + if (check_has_break(stmt->IfStmt.body, label, implicit) || (stmt->IfStmt.else_stmt != nullptr && check_has_break(stmt->IfStmt.else_stmt, label, implicit))) { return true; @@ -199,6 +220,9 @@ gb_internal bool check_has_break(Ast *stmt, String const &label, bool implicit) return check_has_break_list(stmt->CaseClause.stmts, label, implicit); case Ast_SwitchStmt: + if (stmt->SwitchStmt.init && check_has_break_expr(stmt->SwitchStmt.init, label)) { + return true; + } if (label != "" && check_has_break(stmt->SwitchStmt.body, label, false)) { return true; } @@ -211,6 +235,16 @@ gb_internal bool check_has_break(Ast *stmt, String const &label, bool implicit) break; case Ast_ForStmt: + if (stmt->ForStmt.init && check_has_break(stmt->ForStmt.init, label, implicit)) { + return true; + } + if (stmt->ForStmt.cond && check_has_break_expr(stmt->ForStmt.cond, label)) { + return true; + } + if (stmt->ForStmt.post && check_has_break(stmt->ForStmt.post, label, implicit)) { + return true; + } + if (label != "" && check_has_break(stmt->ForStmt.body, label, false)) { return true; } @@ -227,12 +261,35 @@ gb_internal bool check_has_break(Ast *stmt, String const &label, bool implicit) return true; } break; + + case Ast_ValueDecl: + if (stmt->ValueDecl.is_mutable && check_has_break_expr_list(stmt->ValueDecl.values, label)) { + return true; + } + break; + case Ast_AssignStmt: + if (check_has_break_expr_list(stmt->AssignStmt.lhs, label)) { + return true; + } + if (check_has_break_expr_list(stmt->AssignStmt.rhs, label)) { + return true; + } + break; } return false; } - +String label_string(Ast *node) { + GB_ASSERT(node != nullptr); + if (node->kind == Ast_Ident) { + return node->Ident.token.string; + } else if (node->kind == Ast_Label) { + return label_string(node->Label.name); + } + GB_ASSERT("INVALID LABEL"); + return {}; +} // NOTE(bill): The last expression has to be a 'return' statement // TODO(bill): This is a mild hack and should be probably handled properly @@ -243,13 +300,27 @@ gb_internal bool check_is_terminating(Ast *node, String const &label) { case_end; case_ast_node(bs, BlockStmt, node); - return check_is_terminating_list(bs->stmts, label); + if (check_is_terminating_list(bs->stmts, label)) { + if (bs->label != nullptr) { + return check_is_terminating_list(bs->stmts, label_string(bs->label)); + } + return true; + } case_end; case_ast_node(es, ExprStmt, node); return check_is_terminating(unparen_expr(es->expr), label); case_end; + case_ast_node(vd, ValueDecl, node); + return check_has_break_expr_list(vd->values, label); + case_end; + + case_ast_node(as, AssignStmt, node); + return check_has_break_expr_list(as->lhs, label) || + check_has_break_expr_list(as->rhs, label); + case_end; + case_ast_node(bs, BranchStmt, node); return bs->token.kind == Token_fallthrough; case_end; @@ -291,6 +362,9 @@ gb_internal bool check_is_terminating(Ast *node, String const &label) { case_ast_node(fs, ForStmt, node); if (fs->cond == nullptr && !check_has_break(fs->body, label, true)) { + if (fs->label) { + return !check_has_break(fs->body, label_string(fs->label), false); + } return true; } case_end; @@ -427,7 +501,9 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O return nullptr; case Addressing_Variable: - check_old_for_or_switch_value_usage(lhs->expr); + if (e && e->kind == Entity_Variable && e->Variable.is_rodata) { + error(lhs->expr, "Assignment to variable '%.*s' marked as @(rodata) is not allowed", LIT(e->token.string)); + } break; case Addressing_MapIndex: { @@ -449,9 +525,8 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O break; } - case Addressing_Context: { + case Addressing_Context: break; - } case Addressing_SoaVariable: break; @@ -493,14 +568,18 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O } else { error(lhs->expr, "Cannot assign to '%s' which is a procedure parameter", str); } - error_line("\tSuggestion: Did you mean to pass '%.*s' by pointer?\n", LIT(e->token.string)); + if (is_type_pointer(e->type)) { + error_line("\tSuggestion: Did you mean to shadow it? '%.*s := %.*s'?\n", LIT(e->token.string), LIT(e->token.string)); + } else { + error_line("\tSuggestion: Did you mean to pass '%.*s' by pointer?\n", LIT(e->token.string)); + } show_error_on_line(e->token.pos, token_pos_end(e->token)); } else { ERROR_BLOCK(); error(lhs->expr, "Cannot assign to '%s'", str); if (e && e->flags & EntityFlag_ForValue) { - isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token), "Suggestion:"); + isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token)); if (offset < 0) { if (is_type_map(e->type)) { error_line("\tSuggestion: Did you mean? 'for key, &%.*s in ...'\n", LIT(e->token.string)); @@ -516,7 +595,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O } } else if (e && e->flags & EntityFlag_SwitchValue) { - isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token), "Suggestion:"); + isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token)); if (offset < 0) { error_line("\tSuggestion: Did you mean? 'switch &%.*s in ...'\n", LIT(e->token.string)); } else { @@ -704,7 +783,7 @@ gb_internal bool check_using_stmt_entity(CheckerContext *ctx, AstUsingStmt *us, for (auto const &entry : scope->elements) { String name = entry.key; Entity *decl = entry.value; - if (!is_entity_exported(decl)) continue; + if (!is_entity_exported(decl, true)) continue; Entity *found = scope_insert_with_name(ctx->scope, name, decl); if (found != nullptr) { @@ -729,6 +808,8 @@ gb_internal bool check_using_stmt_entity(CheckerContext *ctx, AstUsingStmt *us, bool is_ptr = is_type_pointer(e->type); Type *t = base_type(type_deref(e->type)); if (t->kind == Type_Struct) { + wait_signal_until_available(&t->Struct.fields_wait_signal); + Scope *found = t->Struct.scope; GB_ASSERT(found != nullptr); for (auto const &entry : found->elements) { @@ -979,6 +1060,9 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags if (ss->tag != nullptr) { check_expr(ctx, &x, ss->tag); check_assignment(ctx, &x, nullptr, str_lit("switch expression")); + if (x.type == nullptr) { + return; + } } else { x.mode = Addressing_Constant; x.type = t_bool; @@ -1174,11 +1258,23 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags error_line("\t%.*s\n", LIT(f->token.string)); } } - error_line("\n"); - error_line("\tSuggestion: Was '#partial switch' wanted?\n"); } } + + if (build_context.strict_style) { + Token stok = ss->token; + for_array(i, bs->stmts) { + Ast *stmt = bs->stmts[i]; + if (stmt->kind != Ast_CaseClause) { + continue; + } + Token ctok = stmt->CaseClause.token; + if (ctok.pos.column > stok.pos.column) { + error(ctok, "With '-strict-style', 'case' statements must share the same column as the 'switch' token"); + } + } + } } @@ -1252,7 +1348,6 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_ } } - bool is_ptr = is_type_pointer(x.type); // NOTE(bill): Check for multiple defaults Ast *first_default = nullptr; @@ -1371,15 +1466,6 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_ } bool is_reference = is_addressed; - bool old_style = false; - - if (!is_reference && - is_ptr && - cc->list.count == 1 && - case_type != nullptr) { - is_reference = true; - old_style = true; - } if (cc->list.count > 1 || saw_nil) { case_type = nullptr; @@ -1401,9 +1487,6 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_ if (!is_reference) { tag_var->flags |= EntityFlag_Value; } - if (old_style) { - tag_var->flags |= EntityFlag_OldForOrSwitchValue; - } add_entity(ctx, ctx->scope, lhs, tag_var); add_entity_use(ctx, lhs, tag_var); add_implicit_entity(ctx, stmt, tag_var); @@ -1542,7 +1625,6 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) auto entities = array_make(temporary_allocator(), 0, 2); bool is_map = false; bool is_bit_set = false; - bool use_by_reference_for_value = false; bool is_soa = false; bool is_reverse = rs->reverse; @@ -1603,7 +1685,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) } } } - bool is_ptr = is_type_pointer(operand.type); + bool is_ptr = type_deref(operand.type); Type *t = base_type(type_deref(operand.type)); switch (t->kind) { @@ -1629,52 +1711,77 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) if (build_context.no_rtti && is_type_enum(t->BitSet.elem)) { error(node, "Iteration over a bit_set of an enum is not allowed runtime type information (RTTI) has been disallowed"); } + if (rs->vals.count == 1 && rs->vals[0] && rs->vals[0]->kind == Ast_Ident) { + String name = rs->vals[0]->Ident.token.string; + Entity *found = scope_lookup(ctx->scope, name); + if (found && are_types_identical(found->type, t->BitSet.elem)) { + ERROR_BLOCK(); + gbString s = expr_to_string(expr); + error(rs->vals[0], "'%.*s' shadows a previous declaration which might be ambiguous with 'for (%.*s in %s)'", LIT(name), LIT(name), s); + error_line("\tSuggestion: Use a different identifier if iteration is wanted, or surround in parentheses if a normal for loop is wanted\n"); + gb_string_free(s); + } + } break; case Type_EnumeratedArray: - if (is_ptr) use_by_reference_for_value = true; array_add(&vals, t->EnumeratedArray.elem); array_add(&vals, t->EnumeratedArray.index); break; case Type_Array: - if (is_ptr) use_by_reference_for_value = true; - if (!is_ptr) is_possibly_addressable = operand.mode == Addressing_Variable; + is_possibly_addressable = operand.mode == Addressing_Variable || is_ptr; array_add(&vals, t->Array.elem); array_add(&vals, t_int); break; case Type_DynamicArray: - if (is_ptr) use_by_reference_for_value = true; array_add(&vals, t->DynamicArray.elem); array_add(&vals, t_int); break; case Type_Slice: - if (is_ptr) use_by_reference_for_value = true; array_add(&vals, t->Slice.elem); array_add(&vals, t_int); break; case Type_Map: - if (is_ptr) use_by_reference_for_value = true; is_map = true; array_add(&vals, t->Map.key); array_add(&vals, t->Map.value); if (is_reverse) { error(node, "#reverse for is not supported for map types, as maps are unordered"); } + if (rs->vals.count == 1 && rs->vals[0] && rs->vals[0]->kind == Ast_Ident) { + String name = rs->vals[0]->Ident.token.string; + Entity *found = scope_lookup(ctx->scope, name); + if (found && are_types_identical(found->type, t->Map.key)) { + ERROR_BLOCK(); + gbString s = expr_to_string(expr); + error(rs->vals[0], "'%.*s' shadows a previous declaration which might be ambiguous with 'for (%.*s in %s)'", LIT(name), LIT(name), s); + error_line("\tSuggestion: Use a different identifier if iteration is wanted, or surround in parentheses if a normal for loop is wanted\n"); + gb_string_free(s); + } + } break; case Type_Tuple: { isize count = t->Tuple.variables.count; - if (count < 1 || count > 3) { + if (count < 1) { ERROR_BLOCK(); check_not_tuple(ctx, &operand); - error_line("\tMultiple return valued parameters in a range statement are limited to a maximum of 2 usable values with a trailing boolean for the conditional\n"); + error_line("\tMultiple return valued parameters in a range statement are limited to a minimum of 1 usable values with a trailing boolean for the conditional, got %td\n", count); break; } + enum : isize {MAXIMUM_COUNT = 100}; + if (count > MAXIMUM_COUNT) { + ERROR_BLOCK(); + check_not_tuple(ctx, &operand); + error_line("\tMultiple return valued parameters in a range statement are limited to a maximum of %td usable values with a trailing boolean for the conditional, got %td\n", MAXIMUM_COUNT, count); + break; + } + Type *cond_type = t->Tuple.variables[count-1]->type; if (!is_type_boolean(cond_type)) { gbString s = type_to_string(cond_type); @@ -1683,24 +1790,23 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) break; } + max_val_count = count; + for (Entity *e : t->Tuple.variables) { array_add(&vals, e->type); } is_possibly_addressable = false; - if (rs->vals.count > 1 && rs->vals[1] != nullptr && count < 3) { - gbString s = type_to_string(t); - error(operand.expr, "Expected a 3-valued expression on the rhs, got (%s)", s); - gb_string_free(s); - break; - } - - if (rs->vals.count > 0 && rs->vals[0] != nullptr && count < 2) { - gbString s = type_to_string(t); - error(operand.expr, "Expected at least a 2-valued expression on the rhs, got (%s)", s); - gb_string_free(s); - break; + bool do_break = false; + for (isize i = rs->vals.count-1; i >= 0; i--) { + if (rs->vals[i] != nullptr && count < i+2) { + gbString s = type_to_string(t); + error(operand.expr, "Expected a %td-valued expression on the rhs, got (%s)", i+2, s); + gb_string_free(s); + do_break = true; + break; + } } if (is_reverse) { @@ -1712,7 +1818,6 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) case Type_Struct: if (t->Struct.soa_kind != StructSoa_None) { is_soa = true; - if (is_ptr) use_by_reference_for_value = true; array_add(&vals, t->Struct.soa_elem); array_add(&vals, t_int); } @@ -1789,9 +1894,6 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) char const *idx_name = is_map ? "key" : is_bit_set ? "element" : "index"; error(token, "The %s variable '%.*s' cannot be made addressable", idx_name, LIT(str)); } - } else if (i == addressable_index && use_by_reference_for_value) { - entity->flags |= EntityFlag_OldForOrSwitchValue; - entity->flags &= ~EntityFlag_Value; } if (is_soa) { if (i == 0) { @@ -1932,11 +2034,17 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f gb_string_free(str); init_type = t_invalid; } + if (init_type == t_invalid && entity_count == 1 && (mod_flags & (Stmt_BreakAllowed|Stmt_FallthroughAllowed))) { + Entity *e = entities[0]; + if (e != nullptr && e->token.string == "default") { + warning(e->token, "Did you mean 'case:'?"); + } + } } // TODO NOTE(bill): This technically checks things multple times - AttributeContext ac = make_attribute_context(ctx->foreign_context.link_prefix); + AttributeContext ac = make_attribute_context(ctx->foreign_context.link_prefix, ctx->foreign_context.link_suffix); check_decl_attributes(ctx, vd->attributes, var_decl_attribute, &ac); for (isize i = 0; i < entity_count; i++) { @@ -1953,7 +2061,7 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f e->type = init_type; e->state = EntityState_Resolved; } - ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix); + ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix, ac.link_suffix); if (ac.link_name.len > 0) { e->Variable.link_name = ac.link_name; @@ -1971,6 +2079,13 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f } } } + if (ac.rodata) { + if (ac.is_static) { + e->Variable.is_rodata = true; + } else { + error(e->token, "Only global or @(static) variables can have @(rodata) applied"); + } + } if (ac.thread_local_model != "") { String name = e->token.string; if (name == "_") { @@ -2132,8 +2247,16 @@ gb_internal void check_expr_stmt(CheckerContext *ctx, Ast *node) { } if (do_require) { gbString expr_str = expr_to_string(ce->proc); + defer (gb_string_free(expr_str)); + if (builtin_id) { + String real_name = builtin_procs[builtin_id].name; + if (real_name != make_string(cast(u8 const *)expr_str, gb_string_length(expr_str))) { + error(node, "'%s' ('%.*s.%.*s') requires that its results must be handled", expr_str, + LIT(builtin_proc_pkg_name[builtin_procs[builtin_id].pkg]), LIT(real_name)); + return; + } + } error(node, "'%s' requires that its results must be handled", expr_str); - gb_string_free(expr_str); } return; } else if (expr && expr->kind == Ast_SelectorCallExpr) { @@ -2409,6 +2532,10 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { unsafe_return_error(o, "the address of an indexed variable", f->type); } } + } else if (o.mode == Addressing_Constant && is_type_slice(o.type)) { + ERROR_BLOCK(); + unsafe_return_error(o, "a compound literal of a slice"); + error_line("\tNote: A constant slice value will use the memory of the current stack frame\n"); } } diff --git a/src/check_type.cpp b/src/check_type.cpp index a6dbb8dfc..e3609970a 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -19,10 +19,12 @@ gb_internal void populate_using_array_index(CheckerContext *ctx, Ast *node, AstF } } else { Token tok = make_token_ident(name); - if (field->names.count > 0) { - tok.pos = ast_token(field->names[0]).pos; - } else { - tok.pos = ast_token(field->type).pos; + if (field) { + if (field->names.count > 0) { + tok.pos = ast_token(field->names[0]).pos; + } else { + tok.pos = ast_token(field->type).pos; + } } Entity *f = alloc_entity_array_elem(nullptr, tok, t->Array.elem, idx); add_entity(ctx, ctx->scope, nullptr, f); @@ -191,9 +193,10 @@ gb_internal void check_struct_fields(CheckerContext *ctx, Ast *node, Slicenames.count > 0) { Type *first_type = fields_array[fields_array.count-1]->type; + bool soa_ptr = is_type_soa_pointer(first_type); Type *t = base_type(type_deref(first_type)); - if (!does_field_type_allow_using(t) && + if ((soa_ptr || !does_field_type_allow_using(t)) && p->names.count >= 1 && p->names[0]->kind == Ast_Ident) { Token name_token = p->names[0]->Ident.token; @@ -332,7 +335,7 @@ bool check_constant_parameter_value(Type *type, Ast *expr) { gb_internal Type *check_record_polymorphic_params(CheckerContext *ctx, Ast *polymorphic_params, bool *is_polymorphic_, - Ast *node, Array *poly_operands) { + Array *poly_operands) { Type *polymorphic_params_type = nullptr; GB_ASSERT(is_polymorphic_ != nullptr); @@ -381,6 +384,7 @@ gb_internal Type *check_record_polymorphic_params(CheckerContext *ctx, Ast *poly Type *type = nullptr; bool is_type_param = false; bool is_type_polymorphic_type = false; + Type *specialization = nullptr; if (type_expr == nullptr && default_value == nullptr) { error(param, "Expected a type for this parameter"); continue; @@ -393,7 +397,6 @@ gb_internal Type *check_record_polymorphic_params(CheckerContext *ctx, Ast *poly } if (type_expr->kind == Ast_TypeidType) { is_type_param = true; - Type *specialization = nullptr; if (type_expr->TypeidType.specialization != nullptr) { Ast *s = type_expr->TypeidType.specialization; specialization = check_type(ctx, s); @@ -471,6 +474,15 @@ gb_internal Type *check_record_polymorphic_params(CheckerContext *ctx, Ast *poly if (is_type_polymorphic(base_type(operand.type))) { *is_polymorphic_ = true; can_check_fields = false; + } else if (specialization && + !check_type_specialization_to(ctx, specialization, operand.type, false, /*modify_type*/true)) { + if (!ctx->no_polymorphic_errors) { + gbString t = type_to_string(operand.type); + gbString s = type_to_string(specialization); + error(operand.expr, "Cannot convert type '%s' to the specialization '%s'", t, s); + gb_string_free(s); + gb_string_free(t); + } } e = alloc_entity_type_name(scope, token, operand.type); e->TypeName.is_type_alias = true; @@ -552,19 +564,7 @@ gb_internal bool check_record_poly_operand_specialization(CheckerContext *ctx, T gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types, isize param_count, Array const &ordered_operands) { for (Entity *e : found_gen_types->types) { Type *t = base_type(e->type); - TypeTuple *tuple = nullptr; - switch (t->kind) { - case Type_Struct: - if (t->Struct.polymorphic_params) { - tuple = &t->Struct.polymorphic_params->Tuple; - } - break; - case Type_Union: - if (t->Union.polymorphic_params) { - tuple = &t->Union.polymorphic_params->Tuple; - } - break; - } + TypeTuple *tuple = get_record_polymorphic_params(t); GB_ASSERT_MSG(tuple != nullptr, "%s :: %s", type_to_string(e->type), type_to_string(t)); GB_ASSERT(param_count == tuple->variables.count); @@ -643,14 +643,17 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * context = str_lit("struct #raw_union"); } + struct_type->Struct.node = node; struct_type->Struct.scope = ctx->scope; struct_type->Struct.is_packed = st->is_packed; struct_type->Struct.is_no_copy = st->is_no_copy; struct_type->Struct.polymorphic_params = check_record_polymorphic_params( ctx, st->polymorphic_params, &struct_type->Struct.is_polymorphic, - node, poly_operands + poly_operands ); + wait_signal_set(&struct_type->Struct.polymorphic_wait_signal); + struct_type->Struct.is_poly_specialized = check_record_poly_operand_specialization(ctx, struct_type, poly_operands, &struct_type->Struct.is_polymorphic); if (original_type_for_poly) { GB_ASSERT(named_type != nullptr); @@ -694,12 +697,15 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no ast_node(ut, UnionType, node); + union_type->Union.node = node; union_type->Union.scope = ctx->scope; union_type->Union.polymorphic_params = check_record_polymorphic_params( ctx, ut->polymorphic_params, &union_type->Union.is_polymorphic, - node, poly_operands + poly_operands ); + wait_signal_set(&union_type->Union.polymorphic_wait_signal); + union_type->Union.is_poly_specialized = check_record_poly_operand_specialization(ctx, union_type, poly_operands, &union_type->Union.is_polymorphic); if (original_type_for_poly) { GB_ASSERT(named_type != nullptr); @@ -734,7 +740,7 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no gb_string_free(str); } else { for_array(j, variants) { - if (are_types_identical(t, variants[j])) { + if (union_variant_index_types_equal(t, variants[j])) { ok = false; ERROR_BLOCK(); gbString str = type_to_string(t); @@ -772,7 +778,7 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no } } if (variants.count < 2) { - error(ut->align, "A union with #no_nil must have at least 2 variants"); + error(node, "A union with #no_nil must have at least 2 variants"); } break; } @@ -797,11 +803,11 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam enum_type->Enum.scope = ctx->scope; Type *base_type = t_int; - if (et->base_type != nullptr) { + if (unparen_expr(et->base_type) != nullptr) { base_type = check_type(ctx, et->base_type); } - if (base_type == nullptr || !is_type_integer(base_type)) { + if (base_type == nullptr || base_type == t_invalid || !is_type_integer(base_type)) { error(node, "Base type for enumeration must be an integer"); return; } @@ -933,35 +939,24 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam enum_type->Enum.max_value_index = max_value_index; } -gb_internal bool is_valid_bit_field_backing_type(Type *type) { - if (type == nullptr) { - return false; - } - type = base_type(type); - if (is_type_untyped(type)) { - return false; - } - if (is_type_integer(type)) { - return true; - } - if (type->kind == Type_Array) { - return is_type_integer(type->Array.elem); - } - return false; -} gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, Type *named_type, Ast *node) { ast_node(bf, BitFieldType, node); GB_ASSERT(is_type_bit_field(bit_field_type)); Type *backing_type = check_type(ctx, bf->backing_type); - if (backing_type == nullptr || !is_valid_bit_field_backing_type(backing_type)) { - error(node, "Backing type for a bit_field must be an integer or an array of an integer"); + + bit_field_type->BitField.backing_type = backing_type ? backing_type : t_u8; + bit_field_type->BitField.scope = ctx->scope; + + if (backing_type == nullptr) { + error(bf->backing_type, "Backing type for a bit_field must be an integer or an array of an integer"); + return; + } + if (!is_valid_bit_field_backing_type(backing_type)) { + error(bf->backing_type, "Backing type for a bit_field must be an integer or an array of an integer"); return; } - - bit_field_type->BitField.backing_type = backing_type; - bit_field_type->BitField.scope = ctx->scope; auto fields = array_make(permanent_allocator(), 0, bf->fields.count); auto bit_sizes = array_make (permanent_allocator(), 0, bf->fields.count); @@ -1075,6 +1070,8 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, array_add(&tags, tag); add_entity_use(ctx, field, e); + + total_bit_size += bit_size_u8; } } @@ -1089,13 +1086,57 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, if (total_bit_size > maximum_bit_size) { gbString s = type_to_string(backing_type); - error(node, "The numbers required %llu exceeds the backing type's (%s) bit size %llu", + error(node, "The total bit size of a bit_field's fields (%llu) must fit into its backing type's (%s) bit size of %llu", cast(unsigned long long)total_bit_size, s, cast(unsigned long long)maximum_bit_size); gb_string_free(s); } + enum EndianKind { + Endian_Unknown, + Endian_Native, + Endian_Little, + Endian_Big, + }; + auto const &determine_endian_kind = [](Type *type) -> EndianKind { + if (is_type_boolean(type)) { + // NOTE(bill): it doesn't matter, and when it does, + // that api is absolutely stupid + return Endian_Unknown; + } else if (type_size_of(type) < 2) { + return Endian_Unknown; + } else if (is_type_endian_specific(type)) { + if (is_type_endian_little(type)) { + return Endian_Little; + } else { + return Endian_Big; + } + } + return Endian_Native; + }; + + Type *backing_type_elem = core_array_type(backing_type); + i64 backing_type_elem_size = type_size_of(backing_type_elem); + EndianKind backing_type_endian_kind = determine_endian_kind(backing_type_elem); + EndianKind endian_kind = Endian_Unknown; + for (Entity *f : fields) { + EndianKind field_kind = determine_endian_kind(f->type); + i64 field_size = type_size_of(f->type); + + if (field_kind && backing_type_endian_kind != field_kind && field_size > 1 && backing_type_elem_size > 1) { + error(f->token, "All 'bit_field' field types must match the same endian kind as the backing type, i.e. all native, all little, or all big"); + } + + if (endian_kind == Endian_Unknown) { + endian_kind = field_kind; + } else if (field_kind && endian_kind != field_kind && field_size > 1) { + error(f->token, "All 'bit_field' field types must be of the same endian variety, i.e. all native, all little, or all big"); + } + } + + + if (bit_sizes.count > 0 && is_type_integer(backing_type)) { bool all_booleans = is_type_boolean(fields[0]->type); bool all_ones = bit_sizes[0] == 1; @@ -1111,7 +1152,7 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, } } if (all_ones && all_booleans) { - if (build_context.vet_flags & VetFlag_Style) { + if (ast_file_vet_style(ctx->file)) { char const *msg = "This 'bit_field' is better expressed as a 'bit_set' since all of the fields are booleans, of 1-bit in size, and the backing type is an integer (-vet-style)"; error(node, msg); } else { @@ -1211,11 +1252,14 @@ gb_internal void check_bit_set_type(CheckerContext *c, Type *type, Type *named_t Type *t = default_type(lhs.type); if (bs->underlying != nullptr) { Type *u = check_type(c, bs->underlying); + // if (!is_valid_bit_field_backing_type(u)) { if (!is_type_integer(u)) { gbString ts = type_to_string(u); error(bs->underlying, "Expected an underlying integer for the bit set, got %s", ts); gb_string_free(ts); - return; + if (!is_valid_bit_field_backing_type(u)) { + return; + } } type->BitSet.underlying = u; } @@ -1384,6 +1428,10 @@ gb_internal bool check_type_specialization_to(CheckerContext *ctx, Type *special bool can_convert = check_cast_internal(ctx, &o, specialization); return can_convert; } else if (t->kind == Type_Struct) { + if (t->Struct.polymorphic_parent == nullptr && + t == s) { + return true; + } if (t->Struct.polymorphic_parent == specialization) { return true; } @@ -1392,8 +1440,8 @@ gb_internal bool check_type_specialization_to(CheckerContext *ctx, Type *special s->Struct.polymorphic_params != nullptr && t->Struct.polymorphic_params != nullptr) { - TypeTuple *s_tuple = &s->Struct.polymorphic_params->Tuple; - TypeTuple *t_tuple = &t->Struct.polymorphic_params->Tuple; + TypeTuple *s_tuple = get_record_polymorphic_params(s); + TypeTuple *t_tuple = get_record_polymorphic_params(t); GB_ASSERT(t_tuple->variables.count == s_tuple->variables.count); for_array(i, s_tuple->variables) { Entity *s_e = s_tuple->variables[i]; @@ -1433,6 +1481,10 @@ gb_internal bool check_type_specialization_to(CheckerContext *ctx, Type *special return true; } } else if (t->kind == Type_Union) { + if (t->Union.polymorphic_parent == nullptr && + t == s) { + return true; + } if (t->Union.polymorphic_parent == specialization) { return true; } @@ -1441,8 +1493,8 @@ gb_internal bool check_type_specialization_to(CheckerContext *ctx, Type *special s->Union.polymorphic_params != nullptr && t->Union.polymorphic_params != nullptr) { - TypeTuple *s_tuple = &s->Union.polymorphic_params->Tuple; - TypeTuple *t_tuple = &t->Union.polymorphic_params->Tuple; + TypeTuple *s_tuple = get_record_polymorphic_params(s); + TypeTuple *t_tuple = get_record_polymorphic_params(t); GB_ASSERT(t_tuple->variables.count == s_tuple->variables.count); for_array(i, s_tuple->variables) { Entity *s_e = s_tuple->variables[i]; @@ -1526,7 +1578,7 @@ gb_internal bool is_expr_from_a_parameter(CheckerContext *ctx, Ast *expr) { return is_expr_from_a_parameter(ctx, lhs); } else if (expr->kind == Ast_Ident) { Operand x= {}; - Entity *e = check_ident(ctx, &x, expr, nullptr, nullptr, false); + Entity *e = check_ident(ctx, &x, expr, nullptr, nullptr, true); if (e->flags & EntityFlag_Param) { return true; } @@ -1700,6 +1752,7 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para if (type_expr == nullptr) { param_value = handle_parameter_value(ctx, nullptr, &type, default_value, true); } else { + Ast *original_type_expr = type_expr; if (type_expr->kind == Ast_Ellipsis) { type_expr = type_expr->Ellipsis.expr; is_variadic = true; @@ -1708,6 +1761,9 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para error(param, "Invalid AST: Invalid variadic parameter with multiple names"); success = false; } + + GB_ASSERT(original_type_expr->kind == Ast_Ellipsis); + type_expr = ast_array_type(type_expr->file(), original_type_expr->Ellipsis.token, nullptr, type_expr); } if (type_expr->kind == Ast_TypeidType) { ast_node(tt, TypeidType, type_expr); @@ -1731,6 +1787,7 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para if (operands != nullptr) { ctx->allow_polymorphic_types = true; } + type = check_type(ctx, type_expr); ctx->allow_polymorphic_types = prev; @@ -1763,12 +1820,12 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para } type = t_invalid; } - if (is_type_empty_union(type)) { - gbString str = type_to_string(type); - error(param, "Invalid use of an empty union '%s'", str); - gb_string_free(str); - type = t_invalid; - } + // if (is_type_empty_union(type)) { + // gbString str = type_to_string(type); + // error(param, "Invalid use of an empty union '%s'", str); + // gb_string_free(str); + // type = t_invalid; + // } if (is_type_polymorphic(type)) { switch (param_value.kind) { @@ -1883,6 +1940,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para error(name, "'#by_ptr' can only be applied to variable fields"); p->flags &= ~FieldFlag_by_ptr; } + if (p->flags&FieldFlag_no_capture) { + error(name, "'#no_capture' can only be applied to variable fields"); + p->flags &= ~FieldFlag_no_capture; + } param = alloc_entity_type_name(scope, name->Ident.token, type, EntityState_Resolved); param->TypeName.is_type_alias = true; @@ -1973,8 +2034,8 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para } if (p->flags&FieldFlag_no_alias) { - if (!is_type_pointer(type)) { - error(name, "'#no_alias' can only be applied pointer typed parameters"); + if (!is_type_pointer(type) && !is_type_multi_pointer(type)) { + error(name, "'#no_alias' can only be applied pointer or multi-pointer typed parameters"); p->flags &= ~FieldFlag_no_alias; // Remove the flag } } @@ -1984,6 +2045,28 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para p->flags &= ~FieldFlag_by_ptr; // Remove the flag } } + if (p->flags&FieldFlag_no_capture) { + if (is_variadic && variadic_index == variables.count) { + if (p->flags & FieldFlag_c_vararg) { + error(name, "'#no_capture' cannot be applied to a #c_vararg parameter"); + p->flags &= ~FieldFlag_no_capture; + } else { + error(name, "'#no_capture' is already implied on all variadic parameter"); + } + } else if (is_type_polymorphic(type)) { + // ignore + } else { + if (is_type_internally_pointer_like(type)) { + error(name, "'#no_capture' is currently reserved for future use"); + } else { + ERROR_BLOCK(); + error(name, "'#no_capture' can only be applied to pointer-like types"); + error_line("\t'#no_capture' does not currently do anything useful\n"); + p->flags &= ~FieldFlag_no_capture; + } + } + } + if (is_poly_name) { if (p->flags&FieldFlag_no_alias) { @@ -2002,6 +2085,11 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para error(name, "'#by_ptr' can only be applied to variable fields"); p->flags &= ~FieldFlag_by_ptr; } + if (p->flags&FieldFlag_no_capture) { + error(name, "'#no_capture' can only be applied to variable fields"); + p->flags &= ~FieldFlag_no_capture; + } + if (!is_type_polymorphic(type) && check_constant_parameter_value(type, params[i])) { // failed @@ -2013,8 +2101,19 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para param = alloc_entity_param(scope, name->Ident.token, type, is_using, true); param->Variable.param_value = param_value; param->Variable.field_group_index = field_group_index; + param->Variable.type_expr = type_expr; } } + + if (is_variadic && variadic_index == variables.count) { + param->flags |= EntityFlag_Ellipsis; + if (is_c_vararg) { + param->flags |= EntityFlag_CVarArg; + } else { + param->flags |= EntityFlag_NoCapture; + } + } + if (p->flags&FieldFlag_no_alias) { param->flags |= EntityFlag_NoAlias; } @@ -2036,6 +2135,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para if (p->flags&FieldFlag_by_ptr) { param->flags |= EntityFlag_ByPtr; } + if (p->flags&FieldFlag_no_capture) { + param->flags |= EntityFlag_NoCapture; + } + param->state = EntityState_Resolved; // NOTE(bill): This should have be resolved whilst determining it add_entity(ctx, scope, name, param); @@ -2049,18 +2152,7 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para if (is_variadic) { GB_ASSERT(variadic_index >= 0); - } - - if (is_variadic) { GB_ASSERT(params.count > 0); - // NOTE(bill): Change last variadic parameter to be a slice - // Custom Calling convention for variadic parameters - Entity *end = variables[variadic_index]; - end->type = alloc_type_slice(end->type); - end->flags |= EntityFlag_Ellipsis; - if (is_c_vararg) { - end->flags |= EntityFlag_CVarArg; - } } isize specialization_count = 0; @@ -2757,12 +2849,15 @@ gb_internal bool complete_soa_type(Checker *checker, Type *t, bool wait_to_finis GB_ASSERT(soa_count >= 0); field_type = alloc_type_array(old_field->type, soa_count); } else { - field_type = alloc_type_pointer(old_field->type); + field_type = alloc_type_multi_pointer(old_field->type); } Entity *new_field = alloc_entity_field(scope, old_field->token, field_type, false, old_field->Variable.field_index); t->Struct.fields[i] = new_field; add_entity(scope, new_field); new_field->flags |= EntityFlag_Used; + if (t->Struct.soa_kind != StructSoa_Fixed) { + new_field->flags |= EntityFlag_SoaPtrField; + } } else { t->Struct.fields[i] = old_field; } @@ -2878,7 +2973,7 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e GB_ASSERT(count >= 0); field_type = alloc_type_array(old_array->Array.elem, count); } else { - field_type = alloc_type_pointer(old_array->Array.elem); + field_type = alloc_type_multi_pointer(old_array->Array.elem); } Token token = {}; token.string = params_xyzw[i]; @@ -2887,6 +2982,9 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e soa_struct->Struct.fields[i] = new_field; add_entity(ctx, scope, nullptr, new_field); add_entity_use(ctx, nullptr, new_field); + if (soa_kind != StructSoa_Fixed) { + new_field->flags |= EntityFlag_SoaPtrField; + } } is_complete = true; @@ -2910,12 +3008,15 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e GB_ASSERT(count >= 0); field_type = alloc_type_array(old_field->type, count); } else { - field_type = alloc_type_pointer(old_field->type); + field_type = alloc_type_multi_pointer(old_field->type); } Entity *new_field = alloc_entity_field(scope, old_field->token, field_type, false, old_field->Variable.field_index); soa_struct->Struct.fields[i] = new_field; add_entity(ctx, scope, nullptr, new_field); add_entity_use(ctx, nullptr, new_field); + if (soa_kind != StructSoa_Fixed) { + new_field->flags |= EntityFlag_SoaPtrField; + } } else { soa_struct->Struct.fields[i] = old_field; } @@ -3219,6 +3320,11 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T case_end; case_ast_node(pe, ParenExpr, e); + if (pe->expr == nullptr) { + error(e, "Expected an expression or type within the parentheses"); + *type = t_invalid; + return true; + } *type = check_type_expr(ctx, pe->expr, named_type); set_base_type(named_type, *type); return true; @@ -3255,6 +3361,10 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T gbString s = expr_to_string(pt->type); error(e, "^ is used for pointer types, did you mean '&%s'?", s); gb_string_free(s); + } else if (is_type_pointer(o.type)) { + gbString s = expr_to_string(pt->type); + error(e, "^ is used for pointer types, did you mean a dereference: '%s^'?", s); + gb_string_free(s); } else { // NOTE(bill): call check_type_expr again to get a consistent error message elem = check_type_expr(&c, pt->type, nullptr); diff --git a/src/checker.cpp b/src/checker.cpp index b7fe2b903..3eae271a0 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -3,6 +3,8 @@ #include "entity.cpp" #include "types.cpp" +String get_final_microarchitecture(); + gb_internal void check_expr(CheckerContext *c, Operand *operand, Ast *expression); gb_internal void check_expr_or_type(CheckerContext *c, Operand *operand, Ast *expression, Type *type_hint=nullptr); gb_internal void add_comparison_procedures_for_fields(CheckerContext *c, Type *t); @@ -48,15 +50,6 @@ gb_internal bool check_rtti_type_disallowed(Ast *expr, Type *type, char const *f return check_rtti_type_disallowed(ast_token(expr), type, format); } -gb_internal void scope_reset(Scope *scope) { - if (scope == nullptr) return; - - rw_mutex_lock(&scope->mutex); - scope->head_child.store(nullptr, std::memory_order_relaxed); - string_map_clear(&scope->elements); - ptr_set_clear(&scope->imported); - rw_mutex_unlock(&scope->mutex); -} gb_internal void scope_reserve(Scope *scope, isize count) { string_map_reserve(&scope->elements, 2*count); @@ -166,9 +159,6 @@ gb_internal void import_graph_node_swap(ImportGraphNode **data, isize i, isize j } - - - gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) { gb_zero_item(d); if (parent) { @@ -182,6 +172,9 @@ gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) { ptr_set_init(&d->deps, 0); ptr_set_init(&d->type_info_deps, 0); d->labels.allocator = heap_allocator(); + d->variadic_reuses.allocator = heap_allocator(); + d->variadic_reuse_max_bytes = 0; + d->variadic_reuse_max_align = 1; } gb_internal DeclInfo *make_decl_info(Scope *scope, DeclInfo *parent) { @@ -344,6 +337,7 @@ gb_internal Scope *scope_of_node(Ast *node) { gb_internal void check_open_scope(CheckerContext *c, Ast *node) { node = unparen_expr(node); + GB_ASSERT(node != nullptr); GB_ASSERT(node->kind == Ast_Invalid || is_ast_stmt(node) || is_ast_type(node)); @@ -378,6 +372,7 @@ gb_internal Entity *scope_lookup_current(Scope *s, String const &name) { return nullptr; } + gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **scope_, Entity **entity_) { if (scope != nullptr) { bool gone_thru_proc = false; @@ -505,9 +500,15 @@ end:; return result; } +gb_global bool in_single_threaded_checker_stage = false; + gb_internal Entity *scope_insert(Scope *s, Entity *entity) { String name = entity->token.string; - return scope_insert_with_name(s, name, entity); + if (in_single_threaded_checker_stage) { + return scope_insert_with_name_no_mutex(s, name, entity); + } else { + return scope_insert_with_name(s, name, entity); + } } gb_internal Entity *scope_insert_no_mutex(Scope *s, Entity *entity) { @@ -516,7 +517,7 @@ gb_internal Entity *scope_insert_no_mutex(Scope *s, Entity *entity) { } -GB_COMPARE_PROC(entity_variable_pos_cmp) { +gb_internal GB_COMPARE_PROC(entity_variable_pos_cmp) { Entity *x = *cast(Entity **)a; Entity *y = *cast(Entity **)b; @@ -652,7 +653,7 @@ gb_internal bool check_vet_shadowing(Checker *c, Entity *e, VettedEntity *ve) { } } - zero_item(ve); + gb_zero_item(ve); ve->kind = VettedEntity_Shadowed; ve->entity = e; ve->other = shadowed; @@ -671,7 +672,7 @@ gb_internal bool check_vet_unused(Checker *c, Entity *e, VettedEntity *ve) { } case Entity_ImportName: case Entity_LibraryName: - zero_item(ve); + gb_zero_item(ve); ve->kind = VettedEntity_Unused; ve->entity = e; return true; @@ -844,6 +845,10 @@ gb_internal void add_declaration_dependency(CheckerContext *c, Entity *e) { if (e == nullptr) { return; } + if (e->flags & EntityFlag_Disabled) { + // ignore the dependencies if it has been `@(disabled=true)` + return; + } if (c->decl != nullptr) { add_dependency(c->info, c->decl, e); } @@ -1013,8 +1018,10 @@ gb_internal void init_universal(void) { {"FreeBSD", TargetOs_freebsd}, {"Haiku", TargetOs_haiku}, {"OpenBSD", TargetOs_openbsd}, + {"NetBSD", TargetOs_netbsd}, {"WASI", TargetOs_wasi}, {"JS", TargetOs_js}, + {"Orca", TargetOs_orca}, {"Freestanding", TargetOs_freestanding}, }; @@ -1038,11 +1045,14 @@ gb_internal void init_universal(void) { add_global_enum_constant(fields, "ODIN_ARCH", bc->metrics.arch); add_global_string_constant("ODIN_ARCH_STRING", target_arch_names[bc->metrics.arch]); } + + add_global_string_constant("ODIN_MICROARCH_STRING", get_final_microarchitecture()); { GlobalEnumValue values[BuildMode_COUNT] = { {"Executable", BuildMode_Executable}, {"Dynamic", BuildMode_DynamicLibrary}, + {"Static", BuildMode_StaticLibrary}, {"Object", BuildMode_Object}, {"Assembly", BuildMode_Assembly}, {"LLVM_IR", BuildMode_LLVM_IR}, @@ -1102,7 +1112,11 @@ gb_internal void init_universal(void) { int minimum_os_version = 0; if (build_context.minimum_os_version_string != "") { int major, minor, revision = 0; + #if defined(GB_SYSTEM_WINDOWS) + sscanf_s(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision); + #else sscanf(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision); + #endif minimum_os_version = (major*10000)+(minor*100)+revision; } add_global_constant("ODIN_MINIMUM_OS_VERSION", t_untyped_integer, exact_value_i64(minimum_os_version)); @@ -1127,6 +1141,17 @@ gb_internal void init_universal(void) { add_global_constant("ODIN_COMPILE_TIMESTAMP", t_untyped_integer, exact_value_i64(odin_compile_timestamp())); + { + String version = {}; + + #ifdef GIT_SHA + version.text = cast(u8 *)GIT_SHA; + version.len = gb_strlen(GIT_SHA); + #endif + + add_global_string_constant("ODIN_VERSION_HASH", version); + } + { bool f16_supported = lb_use_new_pass_system(); if (is_arch_wasm()) { @@ -1164,6 +1189,18 @@ gb_internal void init_universal(void) { add_global_constant("ODIN_SANITIZER_FLAGS", named_type, exact_value_u64(bc->sanitizer_flags)); } + { + GlobalEnumValue values[5] = { + {"None", -1}, + {"Minimal", 0}, + {"Size", 1}, + {"Speed", 2}, + {"Aggressive", 3}, + }; + + auto fields = add_global_enum_type(str_lit("Odin_Optimization_Mode"), values, gb_count_of(values)); + add_global_enum_constant(fields, "ODIN_OPTIMIZATION_MODE", bc->optimization_level); + } // Builtin Procedures @@ -1272,6 +1309,7 @@ gb_internal void init_checker_info(CheckerInfo *i) { array_init(&i->init_procedures, a, 0, 0); array_init(&i->fini_procedures, a, 0, 0); array_init(&i->required_foreign_imports_through_force, a, 0, 0); + array_init(&i->defineables, a); map_init(&i->objc_msgSend_types); string_map_init(&i->load_file_cache); @@ -1281,6 +1319,7 @@ gb_internal void init_checker_info(CheckerInfo *i) { mpsc_init(&i->definition_queue, a); //); // 1<<20); mpsc_init(&i->required_global_variable_queue, a); // 1<<10); mpsc_init(&i->required_foreign_imports_through_force_queue, a); // 1<<10); + mpsc_init(&i->foreign_imports_to_check_fullpaths, a); // 1<<10); mpsc_init(&i->intrinsics_entry_point_usage, a); // 1<<10); // just waste some memory here, even if it probably never used string_map_init(&i->load_directory_cache); @@ -1300,11 +1339,13 @@ gb_internal void destroy_checker_info(CheckerInfo *i) { string_map_destroy(&i->packages); array_free(&i->variable_init_order); array_free(&i->required_foreign_imports_through_force); + array_free(&i->defineables); mpsc_destroy(&i->entity_queue); mpsc_destroy(&i->definition_queue); mpsc_destroy(&i->required_global_variable_queue); mpsc_destroy(&i->required_foreign_imports_through_force_queue); + mpsc_destroy(&i->foreign_imports_to_check_fullpaths); map_destroy(&i->objc_msgSend_types); string_map_destroy(&i->load_file_cache); @@ -1347,7 +1388,7 @@ gb_internal void reset_checker_context(CheckerContext *ctx, AstFile *file, Untyp auto type_path = ctx->type_path; array_clear(type_path); - zero_size(&ctx->pkg, gb_size_of(CheckerContext) - gb_offset_of(CheckerContext, pkg)); + gb_zero_size(&ctx->pkg, gb_size_of(CheckerContext) - gb_offset_of(CheckerContext, pkg)); ctx->file = nullptr; ctx->scope = builtin_pkg->scope; @@ -1431,6 +1472,7 @@ gb_internal Entity *implicit_entity_of_node(Ast *clause) { } gb_internal Entity *entity_of_node(Ast *expr) { +retry:; expr = unparen_expr(expr); switch (expr->kind) { case_ast_node(ident, Ident, expr); @@ -1447,6 +1489,21 @@ gb_internal Entity *entity_of_node(Ast *expr) { case_ast_node(cc, CaseClause, expr); return cc->implicit_entity; case_end; + + case_ast_node(ce, CallExpr, expr); + return ce->entity_procedure_of; + case_end; + + case_ast_node(we, TernaryWhenExpr, expr); + if (we->cond == nullptr) { + break; + } + if (we->cond->tav.value.kind != ExactValue_Bool) { + break; + } + expr = we->cond->tav.value.value_bool ? we->x : we->y; + goto retry; + case_end; } return nullptr; } @@ -1733,8 +1790,7 @@ gb_internal void add_entity_use(CheckerContext *c, Ast *identifier, Entity *enti if (identifier == nullptr || identifier->kind != Ast_Ident) { return; } - Ast *empty_ident = nullptr; - entity->identifier.compare_exchange_strong(empty_ident, identifier); + entity->identifier.store(identifier); identifier->Ident.entity = entity; @@ -1897,8 +1953,7 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { add_type_info_dependency(c->info, c->decl, t); MUTEX_GUARD_BLOCK(&c->info->type_info_mutex) { - MapFindResult fr; - auto found = map_try_get(&c->info->type_info_map, t, &fr); + auto found = map_get(&c->info->type_info_map, t); if (found != nullptr) { // Types have already been added return; @@ -1922,7 +1977,7 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { ti_index = c->info->type_info_types.count; array_add(&c->info->type_info_types, t); } - map_set_internal_from_try_get(&c->checker->info.type_info_map, t, ti_index, fr); + map_set(&c->checker->info.type_info_map, t, ti_index); if (prev) { // NOTE(bill): If a previous one exists already, no need to continue @@ -2193,7 +2248,7 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { // IMPORTANT NOTE(bill): this must be copied as `map_set` takes a const ref // and effectively assigns the `+1` of the value isize const count = set->count; - if (map_set_if_not_previously_exists(set, ti_index, count)) { + if (map_set_if_not_previously_exists(set, ti_index+1, count)) { // Type already exists; return; } @@ -2536,6 +2591,11 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st is_init = false; } + if ((e->flags & EntityFlag_Disabled) != 0) { + warning(e->token, "This @(init) procedure is disabled; you must call it manually"); + is_init = false; + } + if (is_init) { add_dependency_to_set(c, e); array_add(&c->info.init_procedures, e); @@ -2639,6 +2699,10 @@ gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) { str_lit("memmove"), ); + FORCE_ADD_RUNTIME_ENTITIES(build_context.metrics.arch == TargetArch_arm32, + str_lit("aeabi_d2h") + ); + FORCE_ADD_RUNTIME_ENTITIES(is_arch_wasm() && !build_context.tilde_backend, // // Extended data type internal procedures // str_lit("umodti3"), @@ -2926,6 +2990,8 @@ gb_internal void init_core_type_info(Checker *c) { return; } Entity *type_info_entity = find_core_entity(c, str_lit("Type_Info")); + GB_ASSERT(type_info_entity != nullptr); + GB_ASSERT(type_info_entity->type != nullptr); t_type_info = type_info_entity->type; t_type_info_ptr = alloc_type_pointer(t_type_info); @@ -3113,6 +3179,18 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_block_decl_attribute) { error(elem, "Expected a string value for '%.*s'", LIT(name)); } 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)) { + error(elem, "Invalid link suffix: '%.*s'", LIT(link_suffix)); + } else { + c->foreign_context.link_suffix = link_suffix; + } + } else { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; } else if (name == "private") { EntityVisiblityKind kind = EntityVisiblity_PrivateToPackage; if (ev.kind == ExactValue_Invalid) { @@ -3407,6 +3485,18 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { error(elem, "Expected a string value for '%.*s'", LIT(name)); } return true; + } else if (name == "link_suffix") { + 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)) { + error(elem, "Invalid link suffix: %.*s", LIT(ac->link_suffix)); + } + } else { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; } else if (name == "deprecated") { ExactValue ev = check_decl_attribute_value(c, value); @@ -3421,20 +3511,6 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { error(elem, "Expected a string value for '%.*s'", LIT(name)); } return true; - } else if (name == "warning") { - ExactValue ev = check_decl_attribute_value(c, value); - - if (ev.kind == ExactValue_String) { - String msg = ev.value_string; - if (msg.len == 0) { - error(elem, "Warning message cannot be an empty string"); - } else { - ac->warning_message = msg; - } - } else { - error(elem, "Expected a string value for '%.*s'", LIT(name)); - } - return true; } else if (name == "require_results") { if (value != nullptr) { error(elem, "Expected no value for '%.*s'", LIT(name)); @@ -3469,19 +3545,19 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { String mode = ev.value_string; if (mode == "none") { ac->optimization_mode = ProcedureOptimizationMode_None; + } else if (mode == "favor_size") { + ac->optimization_mode = ProcedureOptimizationMode_FavorSize; } else if (mode == "minimal") { - ac->optimization_mode = ProcedureOptimizationMode_Minimal; + error(elem, "Invalid optimization_mode 'minimal' for '%.*s', mode has been removed due to confusion, but 'none' has the same behaviour", LIT(name)); } else if (mode == "size") { - ac->optimization_mode = ProcedureOptimizationMode_Size; + error(elem, "Invalid optimization_mode 'size' for '%.*s', mode has been removed due to confusion, but 'favor_size' has the same behaviour", LIT(name)); } else if (mode == "speed") { - ac->optimization_mode = ProcedureOptimizationMode_Speed; + error(elem, "Invalid optimization_mode 'speed' for '%.*s', mode has been removed due to confusion, but 'favor_size' has the same behaviour", LIT(name)); } else { ERROR_BLOCK(); error(elem, "Invalid optimization_mode for '%.*s'. Valid modes:", LIT(name)); error_line("\tnone\n"); - error_line("\tminimal\n"); - error_line("\tsize\n"); - error_line("\tspeed\n"); + error_line("\tfavor_size\n"); } } else { error(elem, "Expected a string for '%.*s'", LIT(name)); @@ -3589,6 +3665,12 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) { } ac->is_static = true; return true; + } else if (name == "rodata") { + if (value != nullptr) { + error(elem, "'rodata' does not have any parameters"); + } + ac->rodata = true; + return true; } else if (name == "thread_local") { ExactValue ev = check_decl_attribute_value(c, value); if (ac->init_expr_list_count > 0) { @@ -3688,6 +3770,17 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) { error(elem, "Expected a string value for '%.*s'", LIT(name)); } return true; + } else if (name == "link_suffix") { + 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)) { + error(elem, "Invalid link suffix: %.*s", LIT(ac->link_suffix)); + } + } else { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; } else if (name == "link_section") { ExactValue ev = check_decl_attribute_value(c, value); if (ev.kind == ExactValue_String) { @@ -3719,6 +3812,7 @@ gb_internal DECL_ATTRIBUTE_PROC(const_decl_attribute) { name == "linkage" || name == "link_name" || name == "link_prefix" || + name == "link_suffix" || false) { error(elem, "@(%.*s) is not supported for compile time constant value declarations", LIT(name)); return true; @@ -3761,8 +3855,10 @@ gb_internal void check_decl_attributes(CheckerContext *c, Array const &at if (attributes.count == 0) return; String original_link_prefix = {}; + String original_link_suffix = {}; if (ac) { original_link_prefix = ac->link_prefix; + original_link_suffix = ac->link_suffix; } StringSet set = {}; @@ -3821,10 +3917,11 @@ gb_internal void check_decl_attributes(CheckerContext *c, Array const &at } if (!proc(c, elem, name, value, ac)) { - if (!build_context.ignore_unknown_attributes) { + if (!build_context.ignore_unknown_attributes && + !string_set_exists(&build_context.custom_attributes, name)) { ERROR_BLOCK(); error(elem, "Unknown attribute element name '%.*s'", LIT(name)); - error_line("\tDid you forget to use build flag '-ignore-unknown-attributes'?\n"); + error_line("\tDid you forget to use the build flag '-ignore-unknown-attributes' or '-custom-attribute:%.*s'?\n", LIT(name)); } } } @@ -3837,6 +3934,12 @@ gb_internal void check_decl_attributes(CheckerContext *c, Array const &at ac->link_prefix.len = 0; } } + if (ac->link_suffix.text == original_link_suffix.text) { + if (ac->link_name.len > 0) { + ac->link_suffix.text = nullptr; + ac->link_suffix.len = 0; + } + } } } @@ -4014,6 +4117,7 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { bool is_test = false; bool is_init = false; bool is_fini = false; + bool is_priv = false; for_array(i, vd->attributes) { Ast *attr = vd->attributes[i]; @@ -4058,6 +4162,8 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { } if (!success) { error(value, "'%.*s' expects no parameter, or a string literal containing \"file\" or \"package\"", LIT(name)); + } else { + is_priv = true; } @@ -4079,6 +4185,11 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { } } + if (is_priv && is_test) { + error(decl, "Attribute 'private' is not allowed on a test case"); + return; + } + if (entity_visibility_kind == EntityVisiblity_Public && (c->scope->flags&ScopeFlag_File) && c->scope->file) { @@ -4131,6 +4242,7 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { e->Variable.foreign_library_ident = fl; e->Variable.link_prefix = c->foreign_context.link_prefix; + e->Variable.link_suffix = c->foreign_context.link_suffix; } Ast *init_expr = value; @@ -4205,6 +4317,7 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { } } e->Procedure.link_prefix = c->foreign_context.link_prefix; + e->Procedure.link_suffix = c->foreign_context.link_suffix; GB_ASSERT(cc != ProcCC_Invalid); pl->type->ProcType.calling_convention = cc; @@ -4309,17 +4422,22 @@ gb_internal bool correct_single_type_alias(CheckerContext *c, Entity *e) { gb_internal bool correct_type_alias_in_scope_backwards(CheckerContext *c, Scope *s) { bool correction = false; - u32 n = s->elements.count; - for (u32 i = n-1; i < n; i--) { - correction |= correct_single_type_alias(c, s->elements.entries[i].value); + for (u32 n = s->elements.count, i = n-1; i < n; i--) { + auto const &entry = s->elements.entries[i]; + Entity *e = entry.value; + if (entry.hash && e != nullptr) { + correction |= correct_single_type_alias(c, e); + } } return correction; } gb_internal bool correct_type_alias_in_scope_forwards(CheckerContext *c, Scope *s) { bool correction = false; - u32 n = s->elements.count; - for (isize i = 0; i < n; i++) { - correction |= correct_single_type_alias(c, s->elements.entries[i].value); + for (auto const &entry : s->elements) { + Entity *e = entry.value; + if (e != nullptr) { + correction |= correct_single_type_alias(c, entry.value); + } } return correction; } @@ -4467,6 +4585,8 @@ gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d) } gb_internal void check_all_global_entities(Checker *c) { + in_single_threaded_checker_stage = true; + // NOTE(bill): This must be single threaded // Don't bother trying for_array(i, c->info.entities) { @@ -4486,6 +4606,8 @@ gb_internal void check_all_global_entities(Checker *c) { (void)type_align_of(e->type); } } + + in_single_threaded_checker_stage = false; } @@ -4857,6 +4979,82 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_import_decl_attribute) { return false; } +gb_internal void check_foreign_import_fullpaths(Checker *c) { + CheckerContext ctx = make_checker_context(c); + + UntypedExprInfoMap untyped = {}; + defer (map_destroy(&untyped)); + + for (Entity *e = nullptr; mpsc_dequeue(&c->info.foreign_imports_to_check_fullpaths, &e); /**/) { + GB_ASSERT(e != nullptr); + GB_ASSERT(e->kind == Entity_LibraryName); + Ast *decl = e->LibraryName.decl; + ast_node(fl, ForeignImportDecl, decl); + + AstFile *f = decl->file(); + + reset_checker_context(&ctx, f, &untyped); + ctx.collect_delayed_decls = false; + + GB_ASSERT(ctx.scope == e->scope); + + if (fl->fullpaths.count == 0) { + String base_dir = dir_from_path(decl->file()->fullpath); + + auto fullpaths = array_make(permanent_allocator(), 0, fl->filepaths.count); + + for (Ast *fp_node : fl->filepaths) { + Operand op = {}; + check_expr(&ctx, &op, fp_node); + if (op.mode != Addressing_Constant && op.value.kind != ExactValue_String) { + gbString s = expr_to_string(op.expr); + error(fp_node, "Expected a constant string value, got '%s'", s); + gb_string_free(s); + continue; + } + if (!is_type_string(op.type)) { + gbString s = type_to_string(op.type); + error(fp_node, "Expected a constant string value, got value of type '%s'", s); + gb_string_free(s); + continue; + } + + String file_str = op.value.value_string; + file_str = string_trim_whitespace(file_str); + String fullpath = file_str; + if (!is_arch_wasm() || string_ends_with(file_str, str_lit(".o"))) { + String foreign_path = {}; + bool ok = determine_path_from_string(nullptr, decl, base_dir, file_str, &foreign_path, /*use error not syntax_error*/true); + if (ok) { + fullpath = foreign_path; + } + } + array_add(&fullpaths, fullpath); + } + fl->fullpaths = slice_from_array(fullpaths); + } + + for (String const &path : fl->fullpaths) { + String ext = path_extension(path); + if (str_eq_ignore_case(ext, ".c") || + str_eq_ignore_case(ext, ".cpp") || + str_eq_ignore_case(ext, ".cxx") || + str_eq_ignore_case(ext, ".h") || + str_eq_ignore_case(ext, ".hpp") || + str_eq_ignore_case(ext, ".hxx") || + false + ) { + error(fl->token, "With 'foreign import', you cannot import a %.*s file/directory, you must precompile the library and link against that", LIT(ext)); + break; + } + } + + add_untyped_expressions(ctx.info, &untyped); + + e->LibraryName.paths = fl->fullpaths; + } +} + gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { if (decl->state_flags & StateFlag_BeenHandled) return; decl->state_flags |= StateFlag_BeenHandled; @@ -4866,59 +5064,26 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { Scope *parent_scope = ctx->scope; GB_ASSERT(parent_scope->flags&ScopeFlag_File); - GB_ASSERT(fl->fullpaths.count > 0); - String fullpath = fl->fullpaths[0]; - String library_name = path_to_entity_name(fl->library_name.string, fullpath); - if (is_blank_ident(library_name)) { - error(fl->token, "File name, %.*s, cannot be as a library name as it is not a valid identifier", LIT(fl->library_name.string)); + String library_name = fl->library_name.string; + if (library_name.len == 0 && fl->fullpaths.count != 0) { + String fullpath = fl->fullpaths[0]; + library_name = path_to_entity_name(fl->library_name.string, fullpath); + } + if (library_name.len == 0 || is_blank_ident(library_name)) { + error(fl->token, "File name, '%.*s', cannot be as a library name as it is not a valid identifier", LIT(library_name)); return; } - for (String const &path : fl->fullpaths) { - String ext = path_extension(path); - if (str_eq_ignore_case(ext, ".c") || - str_eq_ignore_case(ext, ".cpp") || - str_eq_ignore_case(ext, ".cxx") || - str_eq_ignore_case(ext, ".h") || - str_eq_ignore_case(ext, ".hpp") || - str_eq_ignore_case(ext, ".hxx") || - false - ) { - error(fl->token, "With 'foreign import', you cannot import a %.*s file directory, you must precompile the library and link against that", LIT(ext)); - break; - } - } - - - // if (fl->collection_name != "system") { - // char *c_str = gb_alloc_array(heap_allocator(), char, fullpath.len+1); - // defer (gb_free(heap_allocator(), c_str)); - // gb_memmove(c_str, fullpath.text, fullpath.len); - // c_str[fullpath.len] = '\0'; - - // gbFile f = {}; - // gbFileError file_err = gb_file_open(&f, c_str); - // defer (gb_file_close(&f)); - - // switch (file_err) { - // case gbFileError_Invalid: - // error(decl, "Invalid file or cannot be found ('%.*s')", LIT(fullpath)); - // return; - // case gbFileError_NotExists: - // error(decl, "File cannot be found ('%.*s')", LIT(fullpath)); - // return; - // } - // } GB_ASSERT(fl->library_name.pos.line != 0); fl->library_name.string = library_name; Entity *e = alloc_entity_library_name(parent_scope, fl->library_name, t_invalid, fl->fullpaths, library_name); + e->LibraryName.decl = decl; add_entity_flags_from_file(ctx, e, parent_scope); add_entity(ctx, parent_scope, nullptr, e); - AttributeContext ac = {}; check_decl_attributes(ctx, fl->attributes, foreign_import_decl_attribute, &ac); if (ac.require_declaration) { @@ -4933,12 +5098,8 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { e->LibraryName.extra_linker_flags = extra_linker_flags; } - if (has_asm_extension(fullpath)) { - if (build_context.metrics.arch != TargetArch_amd64 && build_context.metrics.os != TargetOs_darwin) { - error(decl, "Assembly files are not yet supported on this platform: %.*s_%.*s", - LIT(target_os_names[build_context.metrics.os]), LIT(target_arch_names[build_context.metrics.arch])); - } - } + mpsc_enqueue(&ctx->info->foreign_imports_to_check_fullpaths, e); + } // Returns true if a new package is present @@ -5746,35 +5907,6 @@ gb_internal void remove_neighbouring_duplicate_entires_from_sorted_array(Arrayinfo.testing_procedures, init_procedures_cmp); remove_neighbouring_duplicate_entires_from_sorted_array(&c->info.testing_procedures); - - if (build_context.test_names.entries.count == 0) { - return; - } - - AstPackage *pkg = c->info.init_package; - Scope *s = pkg->scope; - - for (String const &name : build_context.test_names) { - Entity *e = scope_lookup(s, name); - if (e == nullptr) { - Token tok = {}; - if (pkg->files.count != 0) { - tok = pkg->files[0]->tokens[0]; - } - error(tok, "Unable to find the test '%.*s' in 'package %.*s' ", LIT(name), LIT(pkg->name)); - } - } - - for (isize i = 0; i < c->info.testing_procedures.count; /**/) { - Entity *e = c->info.testing_procedures[i]; - String name = e->token.string; - if (!string_set_exists(&build_context.test_names, name)) { - array_ordered_remove(&c->info.testing_procedures, i); - } else { - i += 1; - } - } - } @@ -6300,6 +6432,9 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("check procedure bodies"); check_procedure_bodies(c); + TIME_SECTION("check foreign import fullpaths"); + check_foreign_import_fullpaths(c); + TIME_SECTION("add entities from procedure bodies"); check_merge_queues_into_arrays(c); diff --git a/src/checker.hpp b/src/checker.hpp index 2ade9312e..d76e4c7d0 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -51,6 +51,12 @@ enum StmtFlag { enum BuiltinProcPkg { BuiltinProcPkg_builtin, BuiltinProcPkg_intrinsics, + BuiltinProcPkg_COUNT +}; + +String builtin_proc_pkg_name[BuiltinProcPkg_COUNT] = { + str_lit("builtin"), + str_lit("intrinsics"), }; struct BuiltinProc { @@ -112,6 +118,7 @@ enum InstrumentationFlag : i32 { struct AttributeContext { String link_name; String link_prefix; + String link_suffix; String link_section; String linkage; isize init_expr_list_count; @@ -132,6 +139,7 @@ struct AttributeContext { bool entry_point_only : 1; bool instrumentation_enter : 1; bool instrumentation_exit : 1; + bool rodata : 1; u32 optimization_mode; // ProcedureOptimizationMode i64 foreign_import_priority_index; String extra_linker_flags; @@ -146,9 +154,10 @@ struct AttributeContext { String enable_target_feature; // will be enabled for the procedure only }; -gb_internal gb_inline AttributeContext make_attribute_context(String link_prefix) { +gb_internal gb_inline AttributeContext make_attribute_context(String link_prefix, String link_suffix) { AttributeContext ac = {}; ac.link_prefix = link_prefix; + ac.link_suffix = link_suffix; return ac; } @@ -172,6 +181,11 @@ char const *ProcCheckedState_strings[ProcCheckedState_COUNT] { "Checked", }; +struct VariadicReuseData { + Type *slice_type; // ..elem_type + i64 max_count; +}; + // DeclInfo is used to store information of certain declarations to allow for "any order" usage struct DeclInfo { DeclInfo * parent; // NOTE(bill): only used for procedure literals at the moment @@ -210,6 +224,10 @@ struct DeclInfo { Array labels; + Array variadic_reuses; + i64 variadic_reuse_max_bytes; + i64 variadic_reuse_max_align; + // NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time struct lbModule *code_gen_module; }; @@ -302,6 +320,7 @@ struct ForeignContext { Ast * curr_library; ProcCallingConvention default_cc; String link_prefix; + String link_suffix; EntityVisiblityKind visibility_kind; }; @@ -333,7 +352,16 @@ struct ObjcMsgData { ObjcMsgKind kind; Type *proc_type; }; + +enum LoadFileTier { + LoadFileTier_Invalid, + LoadFileTier_Exists, + LoadFileTier_Contents, +}; + struct LoadFileCache { + LoadFileTier tier; + bool exists; String path; gbFileError file_error; String data; @@ -363,6 +391,17 @@ struct GenTypesData { RecursiveMutex mutex; }; +struct Defineable { + String name; + ExactValue default_value; + TokenPos pos; + CommentGroup *docs; + + // These strings are only computed from previous fields when defineables are being shown or exported. + String default_value_str; + String pos_str; +}; + // CheckerInfo stores all the symbol information for a type-checked program struct CheckerInfo { Checker *checker; @@ -389,6 +428,9 @@ struct CheckerInfo { Array entities; Array required_foreign_imports_through_force; + BlockingMutex defineables_mutex; + Array defineables; + // Below are accessed within procedures RwMutex global_untyped_mutex; @@ -414,6 +456,7 @@ struct CheckerInfo { MPSCQueue entity_queue; MPSCQueue required_global_variable_queue; MPSCQueue required_foreign_imports_through_force_queue; + MPSCQueue foreign_imports_to_check_fullpaths; MPSCQueue intrinsics_entry_point_usage; @@ -434,6 +477,8 @@ struct CheckerInfo { BlockingMutex load_directory_mutex; StringMap load_directory_cache; PtrMap load_directory_map; // Key: Ast_CallExpr * + + }; struct CheckerContext { diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index c15ec7137..3a2e1ce22 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -44,6 +44,8 @@ enum BuiltinProcId { // "Intrinsics" BuiltinProc_is_package_imported, + BuiltinProc_has_target_feature, + BuiltinProc_transpose, BuiltinProc_outer_product, BuiltinProc_hadamard_product, @@ -68,6 +70,9 @@ enum BuiltinProcId { BuiltinProc_overflow_sub, BuiltinProc_overflow_mul, + BuiltinProc_add_sat, + BuiltinProc_sub_sat, + BuiltinProc_sqrt, BuiltinProc_fused_mul_add, @@ -190,6 +195,7 @@ BuiltinProc__simd_end, // Platform specific intrinsics BuiltinProc_syscall, + BuiltinProc_syscall_bsd, BuiltinProc_x86_cpuid, BuiltinProc_x86_xgetbv, @@ -254,6 +260,9 @@ BuiltinProc__type_simple_boolean_begin, BuiltinProc__type_simple_boolean_end, + BuiltinProc_type_is_matrix_row_major, + BuiltinProc_type_is_matrix_column_major, + BuiltinProc_type_has_field, BuiltinProc_type_field_type, @@ -267,7 +276,11 @@ BuiltinProc__type_simple_boolean_end, BuiltinProc_type_variant_type_of, BuiltinProc_type_variant_index_of, + BuiltinProc_type_bit_set_elem_type, + BuiltinProc_type_bit_set_underlying_type, + BuiltinProc_type_struct_field_count, + BuiltinProc_type_struct_has_implicit_padding, BuiltinProc_type_proc_parameter_count, BuiltinProc_type_proc_return_count, @@ -291,6 +304,8 @@ BuiltinProc__type_simple_boolean_end, BuiltinProc__type_end, + BuiltinProc_procedure_of, + BuiltinProc___entry_point, BuiltinProc_objc_send, @@ -354,6 +369,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { // "Intrinsics" {STR_LIT("is_package_imported"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("has_target_feature"), 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}, @@ -379,6 +396,9 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("overflow_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("overflow_mul"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("add_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("sub_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("sqrt"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("fused_mul_add"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -500,7 +520,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("syscall"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("syscall"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("syscall_bsd"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("x86_cpuid"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("x86_xgetbv"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -560,6 +581,9 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("type_has_nil"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_is_matrix_row_major"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_is_matrix_column_major"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_has_field"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_field_type"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -573,7 +597,11 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("type_variant_type_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_variant_index_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("type_struct_field_count"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_bit_set_elem_type"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_bit_set_underlying_type"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + + {STR_LIT("type_struct_field_count"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_struct_has_implicit_padding"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_proc_parameter_count"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_proc_return_count"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -598,6 +626,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("procedure_of"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("__entry_point"), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("objc_send"), 3, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, diff --git a/src/common.cpp b/src/common.cpp index 69426e2a6..0ef39bd10 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -14,6 +14,8 @@ #undef NOMINMAX #endif +#include + #define GB_WINDOWS_H_INCLUDED #define GB_IMPLEMENTATION #include "gb/gb.h" diff --git a/src/common_memory.cpp b/src/common_memory.cpp index 60e570eee..47b2796a9 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -2,13 +2,6 @@ #include #endif -gb_internal gb_inline void zero_size(void *ptr, isize len) { - memset(ptr, 0, len); -} - -#define zero_item(ptr) zero_size((ptr), gb_size_of(ptr)) - - template gb_internal gb_inline U bit_cast(V &v) { return reinterpret_cast(v); } @@ -39,6 +32,8 @@ gb_internal void virtual_memory_init(void) { } +gb_internal Thread *get_current_thread(void); + struct MemoryBlock { MemoryBlock *prev; @@ -50,8 +45,9 @@ struct MemoryBlock { struct Arena { MemoryBlock * curr_block; isize minimum_block_size; - BlockingMutex mutex; + // BlockingMutex mutex; isize temp_count; + Thread * parent_thread; }; enum { DEFAULT_MINIMUM_BLOCK_SIZE = 8ll*1024ll*1024ll }; @@ -73,10 +69,20 @@ gb_internal isize arena_align_forward_offset(Arena *arena, isize alignment) { return alignment_offset; } +gb_internal void thread_init_arenas(Thread *t) { + t->permanent_arena = gb_alloc_item(heap_allocator(), Arena); + t->temporary_arena = gb_alloc_item(heap_allocator(), Arena); + + t->permanent_arena->parent_thread = t; + t->temporary_arena->parent_thread = t; + + t->permanent_arena->minimum_block_size = DEFAULT_MINIMUM_BLOCK_SIZE; + t->temporary_arena->minimum_block_size = DEFAULT_MINIMUM_BLOCK_SIZE; +} + gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) { GB_ASSERT(gb_is_power_of_two(alignment)); - - mutex_lock(&arena->mutex); + GB_ASSERT(arena->parent_thread == get_current_thread()); isize size = 0; if (arena->curr_block != nullptr) { @@ -102,9 +108,7 @@ gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) { curr_block->used += size; GB_ASSERT(curr_block->used <= curr_block->size); - - mutex_unlock(&arena->mutex); - + // NOTE(bill): memory will be zeroed by default due to virtual memory return ptr; } @@ -259,7 +263,7 @@ struct ArenaTemp { ArenaTemp arena_temp_begin(Arena *arena) { GB_ASSERT(arena); - MUTEX_GUARD(&arena->mutex); + GB_ASSERT(arena->parent_thread == get_current_thread()); ArenaTemp temp = {}; temp.arena = arena; @@ -274,7 +278,7 @@ ArenaTemp arena_temp_begin(Arena *arena) { void arena_temp_end(ArenaTemp const &temp) { GB_ASSERT(temp.arena); Arena *arena = temp.arena; - MUTEX_GUARD(&arena->mutex); + GB_ASSERT(arena->parent_thread == get_current_thread()); if (temp.block) { bool memory_block_found = false; @@ -310,7 +314,7 @@ void arena_temp_end(ArenaTemp const &temp) { void arena_temp_ignore(ArenaTemp const &temp) { GB_ASSERT(temp.arena); Arena *arena = temp.arena; - MUTEX_GUARD(&arena->mutex); + GB_ASSERT(arena->parent_thread == get_current_thread()); GB_ASSERT_MSG(arena->temp_count > 0, "double-use of arena_temp_end"); arena->temp_count -= 1; @@ -370,14 +374,65 @@ gb_internal GB_ALLOCATOR_PROC(arena_allocator_proc) { } -gb_global gb_thread_local Arena permanent_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; -gb_internal gbAllocator permanent_allocator() { - return arena_allocator(&permanent_arena); +enum ThreadArenaKind : uintptr { + ThreadArena_Permanent, + ThreadArena_Temporary, +}; + +gb_global Arena default_permanent_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; +gb_global Arena default_temporary_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; + + +gb_internal Arena *get_arena(ThreadArenaKind kind) { + Thread *t = get_current_thread(); + switch (kind) { + case ThreadArena_Permanent: return t ? t->permanent_arena : &default_permanent_arena; + case ThreadArena_Temporary: return t ? t->temporary_arena : &default_temporary_arena; + } + GB_PANIC("INVALID ARENA KIND"); + return nullptr; +} + + + +gb_internal GB_ALLOCATOR_PROC(thread_arena_allocator_proc) { + void *ptr = nullptr; + ThreadArenaKind kind = cast(ThreadArenaKind)cast(uintptr)allocator_data; + Arena *arena = get_arena(kind); + + switch (type) { + case gbAllocation_Alloc: + ptr = arena_alloc(arena, size, alignment); + break; + case gbAllocation_Free: + break; + case gbAllocation_Resize: + if (size == 0) { + ptr = nullptr; + } else if (size <= old_size) { + ptr = old_memory; + } else { + ptr = arena_alloc(arena, size, alignment); + gb_memmove(ptr, old_memory, old_size); + } + break; + case gbAllocation_FreeAll: + GB_PANIC("use arena_free_all directly"); + arena_free_all(arena); + break; + } + + return ptr; +} + + + +gb_internal gbAllocator permanent_allocator() { + return {thread_arena_allocator_proc, cast(void *)cast(uintptr)ThreadArena_Permanent}; } -gb_global gb_thread_local Arena temporary_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; gb_internal gbAllocator temporary_allocator() { - return arena_allocator(&temporary_arena); + return {thread_arena_allocator_proc, cast(void *)cast(uintptr)ThreadArena_Permanent}; } @@ -385,7 +440,7 @@ gb_internal gbAllocator temporary_allocator() { // #define TEMPORARY_ALLOCATOR_GUARD() -#define TEMPORARY_ALLOCATOR_GUARD() TEMP_ARENA_GUARD(&temporary_arena) +#define TEMPORARY_ALLOCATOR_GUARD() TEMP_ARENA_GUARD(get_arena(ThreadArena_Temporary)) #define PERMANENT_ALLOCATOR_GUARD() diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 9ced78d33..ad8d9d245 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -26,11 +26,11 @@ struct OdinDocWriter { StringMap string_cache; - PtrMap file_cache; - PtrMap pkg_cache; - PtrMap entity_cache; - PtrMap type_cache; - PtrMap stable_type_cache; + OrderedInsertPtrMap file_cache; + OrderedInsertPtrMap pkg_cache; + OrderedInsertPtrMap entity_cache; + OrderedInsertPtrMap type_cache; + OrderedInsertPtrMap stable_type_cache; OdinDocWriterItemTracker files; OdinDocWriterItemTracker pkgs; @@ -57,11 +57,11 @@ gb_internal void odin_doc_writer_prepare(OdinDocWriter *w) { string_map_init(&w->string_cache); - map_init(&w->file_cache); - map_init(&w->pkg_cache); - map_init(&w->entity_cache); - map_init(&w->type_cache); - map_init(&w->stable_type_cache); + map_init(&w->file_cache, 1<<10); + map_init(&w->pkg_cache, 1<<10); + map_init(&w->entity_cache, 1<<18); + map_init(&w->type_cache, 1<<18); + map_init(&w->stable_type_cache, 1<<18); odin_doc_writer_item_tracker_init(&w->files, 1); odin_doc_writer_item_tracker_init(&w->pkgs, 1); @@ -485,6 +485,13 @@ gb_internal OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { return 0; } + if (type->kind == Type_Named) { + Entity *e = type->Named.type_name; + if (e->TypeName.is_type_alias) { + type = type->Named.base; + } + } + // Type **mapped_type = map_get(&w->stable_type_cache, type); // may map to itself // if (mapped_type && *mapped_type) { // type = *mapped_type; @@ -506,13 +513,6 @@ gb_internal OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { if (!x | !y) { continue; } - - if (x->kind == Type_Named) { - Entity *e = x->Named.type_name; - if (e->TypeName.is_type_alias) { - x = x->Named.base; - } - } if (y->kind == Type_Named) { Entity *e = y->Named.type_name; if (e->TypeName.is_type_alias) { @@ -987,9 +987,8 @@ gb_internal void odin_doc_update_entities(OdinDocWriter *w) { auto entities = array_make(heap_allocator(), 0, w->entity_cache.count); defer (array_free(&entities)); - for (u32 i = 0; i < w->entity_cache.count; i++) { - Entity *e = w->entity_cache.entries[i].key; - array_add(&entities, e); + for (auto const &entry : w->entity_cache) { + array_add(&entities, entry.key); } for (Entity *e : entities) { GB_ASSERT(e != nullptr); @@ -998,9 +997,9 @@ gb_internal void odin_doc_update_entities(OdinDocWriter *w) { } } - for (u32 i = 0; i < w->entity_cache.count; i++) { - Entity *e = w->entity_cache.entries[i].key; - OdinDocEntityIndex entity_index = w->entity_cache.entries[i].value; + for (auto const &entry : w->entity_cache) { + Entity *e = entry.key; + OdinDocEntityIndex entity_index = entry.value; OdinDocTypeIndex type_index = odin_doc_type(w, e->type); OdinDocEntityIndex foreign_library = 0; diff --git a/src/entity.cpp b/src/entity.cpp index a12e1d0a6..db6ffdd52 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -45,7 +45,7 @@ enum EntityFlag : u64 { EntityFlag_Value = 1ull<<11, EntityFlag_BitFieldField = 1ull<<12, - + EntityFlag_NoCapture = 1ull<<13, // #no_capture EntityFlag_PolyConst = 1ull<<15, EntityFlag_NotExported = 1ull<<16, @@ -85,8 +85,6 @@ enum EntityFlag : u64 { EntityFlag_Require = 1ull<<50, EntityFlag_ByPtr = 1ull<<51, // enforce parameter is passed by pointer - EntityFlag_OldForOrSwitchValue = 1ull<<52, - EntityFlag_Overridden = 1ull<<63, }; @@ -135,9 +133,7 @@ enum EntityConstantFlags : u32 { enum ProcedureOptimizationMode : u8 { ProcedureOptimizationMode_Default, ProcedureOptimizationMode_None, - ProcedureOptimizationMode_Minimal, - ProcedureOptimizationMode_Size, - ProcedureOptimizationMode_Speed, + ProcedureOptimizationMode_FavorSize, }; @@ -210,6 +206,7 @@ struct Entity { CommentGroup *comment; } Constant; struct { + Ast *type_expr; // only used for some variables within procedure bodies Ast *init_expr; // only used for some variables within procedure bodies i32 field_index; i32 field_group_index; @@ -224,12 +221,14 @@ struct Entity { Ast * foreign_library_ident; String link_name; String link_prefix; + String link_suffix; String link_section; CommentGroup *docs; CommentGroup *comment; bool is_foreign; bool is_export; bool is_global; + bool is_rodata; } Variable; struct { Type * type_parameter_specialization; @@ -244,6 +243,7 @@ struct Entity { Ast * foreign_library_ident; String link_name; String link_prefix; + String link_suffix; DeferredProcedure deferred_procedure; struct GenProcsData *gen_procs; @@ -252,10 +252,9 @@ struct Entity { bool is_foreign : 1; bool is_export : 1; bool generated_from_polymorphic : 1; - bool target_feature_disabled : 1; bool entry_point_only : 1; bool has_instrumentation : 1; - String target_feature; + bool is_memcpy_like : 1; } Procedure; struct { Array entities; @@ -269,6 +268,7 @@ struct Entity { Scope *scope; } ImportName; struct { + Ast *decl; Slice paths; String name; i64 priority_index; @@ -336,6 +336,9 @@ gb_internal Entity *alloc_entity(EntityKind kind, Scope *scope, Token token, Typ entity->token = token; entity->type = type; entity->id = 1 + global_entity_id.fetch_add(1); + if (token.pos.file_id) { + entity->file = thread_safe_get_ast_file_from_id(token.pos.file_id); + } return entity; } @@ -502,4 +505,4 @@ gb_internal bool is_entity_local_variable(Entity *e) { return ((e->scope->flags &~ ScopeFlag_ContextDefined) == 0) || (e->scope->flags & ScopeFlag_Proc) != 0; -} \ No newline at end of file +} diff --git a/src/error.cpp b/src/error.cpp index 8c9fb265b..f95123f15 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -12,7 +12,7 @@ struct ErrorValue { }; struct ErrorCollector { - TokenPos prev; + // TokenPos prev; // no point collecting because of the mulithreaded nature std::atomic count; std::atomic warning_count; std::atomic in_block; @@ -237,6 +237,7 @@ enum TerminalColour { TerminalColour_Blue, TerminalColour_Purple, TerminalColour_Black, + TerminalColour_Grey, }; gb_internal void terminal_set_colours(TerminalStyle style, TerminalColour foreground) { @@ -256,6 +257,7 @@ gb_internal void terminal_set_colours(TerminalStyle style, TerminalColour foregr case TerminalColour_Blue: error_out("\x1b[%s;34m", ss); break; case TerminalColour_Purple: error_out("\x1b[%s;35m", ss); break; case TerminalColour_Black: error_out("\x1b[%s;30m", ss); break; + case TerminalColour_Grey: error_out("\x1b[%s;90m", ss); break; } } } @@ -266,95 +268,240 @@ gb_internal void terminal_reset_colours(void) { } -gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end, char const *prefix=nullptr) { +gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) { get_error_value()->end = end; if (!show_error_line()) { return -1; } - i32 offset = 0; - gbString the_line = get_file_line_as_string(pos, &offset); + i32 error_start_index_bytes = 0; + gbString the_line = get_file_line_as_string(pos, &error_start_index_bytes); defer (gb_string_free(the_line)); - if (the_line != nullptr) { - char const *line_text = the_line; - isize line_len = gb_string_length(the_line); - - // TODO(bill): This assumes ASCII - - enum { - MAX_LINE_LENGTH = 80, - MAX_TAB_WIDTH = 8, - ELLIPSIS_PADDING = 8, // `... ...` - MAX_LINE_LENGTH_PADDED = MAX_LINE_LENGTH-MAX_TAB_WIDTH-ELLIPSIS_PADDING, - }; - - if (prefix) { - error_out("\t%s\n\n", prefix); - } - - error_out("\t"); - - terminal_set_colours(TerminalStyle_Bold, TerminalColour_White); - - - i32 error_length = gb_max(end.offset - pos.offset, 1); - - isize squiggle_extra = 0; - - if (line_len > MAX_LINE_LENGTH_PADDED) { - i32 left = MAX_TAB_WIDTH; - i32 diff = gb_max(offset-left, 0); - if (diff > 0) { - line_text += diff; - line_len -= diff; - offset = left + ELLIPSIS_PADDING/2; - } - if (line_len > MAX_LINE_LENGTH_PADDED) { - line_len = MAX_LINE_LENGTH_PADDED; - if (error_length > line_len-left) { - error_length = cast(i32)line_len - left; - squiggle_extra = 1; - } - } - if (diff > 0) { - error_out("... %.*s ...", cast(i32)line_len, line_text); - } else { - error_out("%.*s ...", cast(i32)line_len, line_text); - } - } else { - error_out("%.*s", cast(i32)line_len, line_text); - } - error_out("\n\t"); - - for (i32 i = 0; i < offset; i++) { - error_out(" "); - } - - terminal_set_colours(TerminalStyle_Bold, TerminalColour_Green); - - error_out("^"); - if (end.file_id == pos.file_id) { - if (end.line > pos.line) { - for (i32 i = offset; i < line_len; i++) { - error_out("~"); - } - } else if (end.line == pos.line && end.column > pos.column) { - for (i32 i = 1; i < error_length-1+squiggle_extra; i++) { - error_out("~"); - } - if (error_length > 1 && squiggle_extra == 0) { - error_out("^"); - } - } - } - + if (the_line == nullptr || gb_string_length(the_line) == 0) { + terminal_set_colours(TerminalStyle_Normal, TerminalColour_Grey); + error_out("\t( empty line )\n"); terminal_reset_colours(); - error_out("\n"); - return offset; + if (the_line == nullptr) { + return -1; + } else { + return cast(isize)error_start_index_bytes; + } } - return -1; + + // These two will be used like an Odin slice later. + char const *line_text = the_line; + i32 line_length_bytes = cast(i32)gb_string_length(the_line); + + ucg_grapheme* graphemes; + i32 line_length_runes = 0; + i32 line_length_graphemes = 0; + i32 line_width = 0; + + int ucg_result = ucg_decode_grapheme_clusters( + permanent_allocator(), (const uint8_t*)line_text, line_length_bytes, + &graphemes, &line_length_runes, &line_length_graphemes, &line_width); + + if (ucg_result < 0) { + // There was a UTF-8 parsing error. + // Insert a dummy grapheme so the start of the invalid rune can be pointed at. + graphemes = (ucg_grapheme*)gb_resize(permanent_allocator(), + graphemes, + sizeof(ucg_grapheme) * (line_length_graphemes), + sizeof(ucg_grapheme) * (1 + line_length_graphemes)); + + ucg_grapheme append = { + error_start_index_bytes, + line_length_runes, + 1, + }; + + graphemes[line_length_graphemes] = append; + } + + // The units below are counted in visual, monospace cells. + enum { + MAX_LINE_LENGTH = 80, + MAX_TAB_WIDTH = 8, + ELLIPSIS_PADDING = 8, // `... ...` + MIN_LEFT_VIEW = 8, + + // A rough estimate of how many characters we'll insert, at most: + MAX_INSERTED_WIDTH = MAX_TAB_WIDTH + ELLIPSIS_PADDING, + + MAX_LINE_LENGTH_PADDED = MAX_LINE_LENGTH - MAX_INSERTED_WIDTH, + }; + + i32 error_start_index_graphemes = 0; + for (i32 i = 0; i < line_length_graphemes; i += 1) { + if (graphemes[i].byte_index == error_start_index_bytes) { + error_start_index_graphemes = i; + break; + } + } + + if (error_start_index_graphemes == 0 && error_start_index_bytes != 0 && line_length_graphemes != 0) { + // The error index in graphemes was not found, but we did find a valid Unicode string. + // + // This is an edge case where the error is sitting on a newline or the + // end of the line, as that is the only location we could not have checked. + error_start_index_graphemes = line_length_graphemes; + } + + error_out("\t"); + + bool show_right_ellipsis = false; + + i32 squiggle_padding = 0; + i32 window_open_bytes = 0; + i32 window_close_bytes = 0; + if (line_width > MAX_LINE_LENGTH_PADDED) { + // Now that we know the line is over the length limit, we have to + // compose a visual window in which to display the error. + i32 window_size_left = 0; + i32 window_size_right = 0; + i32 window_open_graphemes = 0; + + for (i32 i = error_start_index_graphemes - 1; i > 0; i -= 1) { + window_size_left += graphemes[i].width; + if (window_size_left >= MIN_LEFT_VIEW) { + window_open_graphemes = i; + window_open_bytes = graphemes[i].byte_index; + break; + } + } + + for (i32 i = error_start_index_graphemes; i < line_length_graphemes; i += 1) { + window_size_right += graphemes[i].width; + if (window_size_right >= MAX_LINE_LENGTH_PADDED - MIN_LEFT_VIEW) { + window_close_bytes = graphemes[i].byte_index; + break; + } + } + if (window_close_bytes == 0) { + // The window ends at the end of the line. + window_close_bytes = line_length_bytes; + } + + if (window_size_right < MAX_LINE_LENGTH_PADDED - MIN_LEFT_VIEW) { + // Hit the end of the string early on the right side; expand backwards. + for (i32 i = window_open_graphemes - 1; i > 0; i -= 1) { + window_size_left += graphemes[i].width; + if (window_size_left + window_size_right >= MAX_LINE_LENGTH_PADDED) { + window_open_graphemes = i; + window_open_bytes = graphemes[i].byte_index; + break; + } + } + } + + GB_ASSERT_MSG(window_close_bytes >= window_open_bytes, "Error line truncation window has wrong byte indices. (open, close: %i, %i)", window_open_bytes, window_close_bytes); + + if (window_close_bytes != line_length_bytes) { + show_right_ellipsis = true; + } + + // Close the window, going left. + line_length_bytes = window_close_bytes; + + // Adjust the slice of text. In Odin, this would be: + // `line_text = line_text[window_left_bytes:]` + line_text += window_open_bytes; + line_length_bytes -= window_open_bytes; + GB_ASSERT_MSG(line_length_bytes >= 0, "Bounds-checking error: line_length_bytes"); + + if (window_open_bytes > 0) { + error_out("... "); + squiggle_padding += 4; + } + } else { + // No truncation needed. + window_open_bytes = 0; + window_close_bytes = line_length_bytes; + } + + for (i32 i = error_start_index_graphemes; i > 0; i -= 1) { + if (graphemes[i].byte_index == window_open_bytes) { + break; + } + squiggle_padding += graphemes[i].width; + } + + // Start printing code. + + terminal_set_colours(TerminalStyle_Normal, TerminalColour_White); + error_out("%.*s", line_length_bytes, line_text); + + i32 squiggle_length = 0; + bool trailing_squiggle = false; + + if (end.file_id == pos.file_id) { + // The error has an endpoint. + + if (end.line > pos.line) { + // Error goes to next line. + // Always show the ellipsis in this case + show_right_ellipsis = true; + + for (i32 i = error_start_index_graphemes; i < line_length_graphemes; i += 1) { + squiggle_length += graphemes[i].width; + trailing_squiggle = true; + } + + } else if (end.line == pos.line && end.column > pos.column) { + // Error terminates before line end. + i32 adjusted_end_index = graphemes[error_start_index_graphemes].byte_index + end.column - pos.column; + + for (i32 i = error_start_index_graphemes; i < line_length_graphemes; i += 1) { + if (graphemes[i].byte_index >= adjusted_end_index) { + break; + } else if (graphemes[i].byte_index >= window_close_bytes) { + trailing_squiggle = true; + break; + } + squiggle_length += graphemes[i].width; + } + } + } else { + // The error is at one spot; no range known. + squiggle_length = 1; + } + + if (show_right_ellipsis) { + error_out(" ..."); + } + + error_out("\n\t"); + + for (i32 i = squiggle_padding; i > 0; i -= 1) { + error_out(" "); + } + + terminal_set_colours(TerminalStyle_Bold, TerminalColour_Green); + + if (squiggle_length > 0) { + error_out("^"); + squiggle_length -= 1; + } + for (/**/; squiggle_length > 1; squiggle_length -= 1) { + error_out("~"); + } + if (squiggle_length > 0) { + if (trailing_squiggle) { + error_out("~ ..."); + } else { + error_out("^"); + } + } + + // NOTE(Feoramund): Specifically print a newline, then reset colours, + // instead of the other way around. Otherwise the printing mechanism + // will collapse the newline for reasons currently beyond my ken. + error_out("\n"); + terminal_reset_colours(); + + return squiggle_padding; } gb_internal void error_out_empty(void) { @@ -376,30 +523,29 @@ gb_internal void error_out_coloured(char const *str, TerminalStyle style, Termin gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); + mutex_lock(&global_error_collector.mutex); if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) { print_all_errors(); gb_exit(1); } - mutex_lock(&global_error_collector.mutex); push_error_value(pos, ErrorValue_Error); - // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { error_out_empty(); error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); error_out("\n"); - } else if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; - error_out_pos(pos); - if (has_ansi_terminal_colours()) { + } else { + // global_error_collector.prev = pos; + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); } error_out_va(fmt, va); error_out("\n"); show_error_on_line(pos, end); - } else { - global_error_collector.count.fetch_sub(1); } try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); @@ -410,26 +556,31 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, error_va(pos, end, fmt, va); return; } + if (global_ignore_warnings()) { + return; + } + global_error_collector.warning_count.fetch_add(1); mutex_lock(&global_error_collector.mutex); push_error_value(pos, ErrorValue_Warning); - if (!global_ignore_warnings()) { - // NOTE(bill): Duplicate error, skip it - if (pos.line == 0) { + if (pos.line == 0) { + error_out_empty(); + error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); + error_out_va(fmt, va); + error_out("\n"); + } else { + // global_error_collector.prev = pos; + if (json_errors()) { error_out_empty(); - error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); - error_out_va(fmt, va); - error_out("\n"); - } else if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; + } else { error_out_pos(pos); error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); - error_out_va(fmt, va); - error_out("\n"); - show_error_on_line(pos, end); } + error_out_va(fmt, va); + error_out("\n"); + show_error_on_line(pos, end); } try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); @@ -442,22 +593,25 @@ gb_internal void error_line_va(char const *fmt, va_list va) { gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); + mutex_lock(&global_error_collector.mutex); if (global_error_collector.count.load() > MAX_ERROR_COLLECTOR_COUNT()) { print_all_errors(); gb_exit(1); } - mutex_lock(&global_error_collector.mutex); push_error_value(pos, ErrorValue_Error); - // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { error_out_empty(); error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); - } else if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; - error_out_pos(pos); + } else { + // global_error_collector.prev = pos; + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } if (has_ansi_terminal_colours()) { error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); } @@ -471,27 +625,30 @@ gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_li gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); + mutex_lock(&global_error_collector.mutex); if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) { print_all_errors(); gb_exit(1); } - mutex_lock(&global_error_collector.mutex); push_error_value(pos, ErrorValue_Warning); - // NOTE(bill): Duplicate error, skip it - if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; - error_out_pos(pos); - error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); - error_out_va(fmt, va); - error_out("\n"); - show_error_on_line(pos, end); - } else if (pos.line == 0) { + if (pos.line == 0) { error_out_empty(); error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); error_out("\n"); + } else { + // global_error_collector.prev = pos; + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } + error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); + error_out_va(fmt, va); + error_out("\n"); + show_error_on_line(pos, end); } try_pop_error_value(); @@ -500,25 +657,28 @@ gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const * gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); + mutex_lock(&global_error_collector.mutex); if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) { print_all_errors(); gb_exit(1); } - mutex_lock(&global_error_collector.mutex); push_error_value(pos, ErrorValue_Warning); - // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { error_out_empty(); - error_out_coloured("Syntax_Error: ", TerminalStyle_Normal, TerminalColour_Red); + error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); error_out("\n"); - } else if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; - error_out_pos(pos); + } else { + // global_error_collector.prev = pos; + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } if (has_ansi_terminal_colours()) { - error_out_coloured("Syntax_Error: ", TerminalStyle_Normal, TerminalColour_Red); + error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); } error_out_va(fmt, va); error_out("\n"); @@ -535,27 +695,31 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const syntax_error_va(pos, end, fmt, va); return; } + if (global_ignore_warnings()) { + return; + } mutex_lock(&global_error_collector.mutex); global_error_collector.warning_count++; push_error_value(pos, ErrorValue_Warning); - if (!global_ignore_warnings()) { - // NOTE(bill): Duplicate error, skip it - if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; - error_out_pos(pos); - error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); - error_out_va(fmt, va); - error_out("\n"); - // show_error_on_line(pos, end); - } else if (pos.line == 0) { + if (pos.line == 0) { + error_out_empty(); + error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); + error_out_va(fmt, va); + error_out("\n"); + } else { + // global_error_collector.prev = pos; + if (json_errors()) { error_out_empty(); - error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); - error_out_va(fmt, va); - error_out("\n"); + } else { + error_out_pos(pos); } + error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); + error_out_va(fmt, va); + error_out("\n"); + // show_error_on_line(pos, end); } try_pop_error_value(); @@ -656,7 +820,20 @@ gb_internal int error_value_cmp(void const *a, void const *b) { return token_pos_cmp(x->pos, y->pos); } +gb_internal bool errors_already_printed = false; + gb_internal void print_all_errors(void) { + if (errors_already_printed) { + if (global_error_collector.warning_count.load() == global_error_collector.error_values.count) { + for (ErrorValue &ev : global_error_collector.error_values) { + array_free(&ev.msg); + } + array_clear(&global_error_collector.error_values); + errors_already_printed = false; + } + return; + } + auto const &escape_char = [](gbString res, u8 c) -> gbString { switch (c) { case '\n': res = gb_string_append_length(res, "\\n", 2); break; @@ -679,9 +856,46 @@ gb_internal void print_all_errors(void) { GB_ASSERT(any_errors() || any_warnings()); - array_sort(global_error_collector.error_values, error_value_cmp); + + { // NOTE(bill): merge neighbouring errors + isize default_lines_to_skip = 1; + if (show_error_line()) { + // NOTE(bill): this will always be 2 extra lines + default_lines_to_skip += 2; + } + + ErrorValue *prev_ev = nullptr; + for (isize i = 0; i < global_error_collector.error_values.count; /**/) { + ErrorValue &ev = global_error_collector.error_values[i]; + + if (prev_ev && prev_ev->pos == ev.pos) { + String_Iterator it = {{ev.msg.data, ev.msg.count}, 0}; + + for (isize lines_to_skip = default_lines_to_skip; lines_to_skip > 0; lines_to_skip -= 1) { + String line = string_split_iterator(&it, '\n'); + if (line.len == 0) { + break; + } + } + + // Merge additional text (suggestions for example) into the previous error. + String current = {prev_ev->msg.data, prev_ev->msg.count}; + String addition = {it.str.text+it.pos, it.str.len-it.pos}; + if (addition.len > 0 && !string_contains_string(current, addition)) { + array_add_elems(&prev_ev->msg, addition.text, addition.len); + } + + array_free(&ev.msg); + array_ordered_remove(&global_error_collector.error_values, i); + } else { + prev_ev = &ev; + i += 1; + } + } + } + gbString res = gb_string_make(heap_allocator(), ""); defer (gb_string_free(res)); @@ -689,6 +903,7 @@ gb_internal void print_all_errors(void) { res = gb_string_append_fmt(res, "{\n"); res = gb_string_append_fmt(res, "\t\"error_count\": %td,\n", global_error_collector.error_values.count); res = gb_string_append_fmt(res, "\t\"errors\": [\n"); + for_array(i, global_error_collector.error_values) { ErrorValue ev = global_error_collector.error_values[i]; @@ -702,9 +917,8 @@ gb_internal void print_all_errors(void) { } res = gb_string_append_fmt(res, "\",\n"); - res = gb_string_append_fmt(res, "\t\t\t\"pos\": {\n"); - if (ev.pos.file_id) { + res = gb_string_append_fmt(res, "\t\t\t\"pos\": {\n"); res = gb_string_append_fmt(res, "\t\t\t\t\"file\": \""); String file = get_file_path_string(ev.pos.file_id); for (isize k = 0; k < file.len; k++) { @@ -717,6 +931,8 @@ gb_internal void print_all_errors(void) { i32 end_column = gb_max(ev.end.column, ev.pos.column); res = gb_string_append_fmt(res, "\t\t\t\t\"end_column\": %d\n", end_column); res = gb_string_append_fmt(res, "\t\t\t},\n"); + } else { + res = gb_string_append_fmt(res, "\t\t\t\"pos\": null,\n"); } res = gb_string_append_fmt(res, "\t\t\t\"msgs\": [\n"); @@ -772,4 +988,6 @@ gb_internal void print_all_errors(void) { } gbFile *f = gb_file_get_standard(gbFileStandard_Error); gb_file_write(f, res, gb_string_length(res)); -} \ No newline at end of file + + errors_already_printed = true; +} diff --git a/src/exact_value.cpp b/src/exact_value.cpp index b744d2db0..1a42a82a9 100644 --- a/src/exact_value.cpp +++ b/src/exact_value.cpp @@ -54,37 +54,50 @@ gb_global ExactValue const empty_exact_value = {}; gb_internal uintptr hash_exact_value(ExactValue v) { mutex_lock(&hash_exact_value_mutex); defer (mutex_unlock(&hash_exact_value_mutex)); + + uintptr res = 0; switch (v.kind) { case ExactValue_Invalid: return 0; case ExactValue_Bool: - return gb_fnv32a(&v.value_bool, gb_size_of(v.value_bool)); + res = gb_fnv32a(&v.value_bool, gb_size_of(v.value_bool)); + break; case ExactValue_String: - return gb_fnv32a(v.value_string.text, v.value_string.len); + res = gb_fnv32a(v.value_string.text, v.value_string.len); + break; case ExactValue_Integer: { u32 key = gb_fnv32a(v.value_integer.dp, gb_size_of(*v.value_integer.dp) * v.value_integer.used); u8 last = (u8)v.value_integer.sign; - return (key ^ last) * 0x01000193; + res = (key ^ last) * 0x01000193; + break; } case ExactValue_Float: - return gb_fnv32a(&v.value_float, gb_size_of(v.value_float)); + res = gb_fnv32a(&v.value_float, gb_size_of(v.value_float)); + break; case ExactValue_Pointer: - return ptr_map_hash_key(v.value_pointer); + res = ptr_map_hash_key(v.value_pointer); + break; case ExactValue_Complex: - return gb_fnv32a(v.value_complex, gb_size_of(Complex128)); + res = gb_fnv32a(v.value_complex, gb_size_of(Complex128)); + break; case ExactValue_Quaternion: - return gb_fnv32a(v.value_quaternion, gb_size_of(Quaternion256)); + res = gb_fnv32a(v.value_quaternion, gb_size_of(Quaternion256)); + break; case ExactValue_Compound: - return ptr_map_hash_key(v.value_compound); + res = ptr_map_hash_key(v.value_compound); + break; case ExactValue_Procedure: - return ptr_map_hash_key(v.value_procedure); + res = ptr_map_hash_key(v.value_procedure); + break; case ExactValue_Typeid: - return ptr_map_hash_key(v.value_typeid); + res = ptr_map_hash_key(v.value_typeid); + break; + default: + res = gb_fnv32a(&v, gb_size_of(ExactValue)); } - return gb_fnv32a(&v, gb_size_of(ExactValue)); - + return res & 0x7fffffff; } @@ -108,12 +121,14 @@ gb_internal ExactValue exact_value_string(String string) { gb_internal ExactValue exact_value_i64(i64 i) { ExactValue result = {ExactValue_Integer}; + result.value_integer = {0}; big_int_from_i64(&result.value_integer, i); return result; } gb_internal ExactValue exact_value_u64(u64 i) { ExactValue result = {ExactValue_Integer}; + result.value_integer = {0}; big_int_from_u64(&result.value_integer, i); return result; } @@ -164,6 +179,7 @@ gb_internal ExactValue exact_value_typeid(Type *type) { gb_internal ExactValue exact_value_integer_from_string(String const &string) { ExactValue result = {ExactValue_Integer}; + result.value_integer = {0}; bool success; big_int_from_string(&result.value_integer, string, &success); if (!success) { @@ -572,6 +588,7 @@ gb_internal ExactValue exact_unary_operator_value(TokenKind op, ExactValue v, i3 return v; case ExactValue_Integer: { ExactValue i = {ExactValue_Integer}; + i.value_integer = {0}; big_int_neg(&i.value_integer, &v.value_integer); return i; } @@ -603,6 +620,7 @@ gb_internal ExactValue exact_unary_operator_value(TokenKind op, ExactValue v, i3 case ExactValue_Integer: { GB_ASSERT(precision != 0); ExactValue i = {ExactValue_Integer}; + i.value_integer = {0}; big_int_not(&i.value_integer, &v.value_integer, precision, !is_unsigned); return i; } diff --git a/src/gb/gb.h b/src/gb/gb.h index 868e11a16..38dabc9bd 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -83,6 +83,10 @@ extern "C" { #ifndef GB_SYSTEM_OPENBSD #define GB_SYSTEM_OPENBSD 1 #endif + #elif defined(__NetBSD__) + #ifndef GB_SYSTEM_NETBSD + #define GB_SYSTEM_NETBSD 1 + #endif #elif defined(__HAIKU__) || defined(__haiku__) #ifndef GB_SYSTEM_HAIKU #define GB_SYSTEM_HAIKU 1 @@ -208,7 +212,7 @@ extern "C" { #endif #include // NOTE(bill): malloc on linux #include - #if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__HAIKU__) + #if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__HAIKU__) #include #endif #include @@ -250,6 +254,12 @@ extern "C" { #define lseek64 lseek #endif +#if defined(GB_SYSTEM_NETBSD) + #include + #include + #define lseek64 lseek +#endif + #if defined(GB_SYSTEM_HAIKU) #include #include @@ -817,6 +827,13 @@ typedef struct gbAffinity { isize thread_count; isize threads_per_core; } gbAffinity; +#elif defined(GB_SYSTEM_NETBSD) +typedef struct gbAffinity { + b32 is_accurate; + isize core_count; + isize thread_count; + isize threads_per_core; +} gbAffinity; #elif defined(GB_SYSTEM_HAIKU) typedef struct gbAffinity { b32 is_accurate; @@ -2517,7 +2534,7 @@ gb_inline void const *gb_pointer_add_const(void const *ptr, isize bytes) { gb_inline void const *gb_pointer_sub_const(void const *ptr, isize bytes) { return cast(void const *)(cast(u8 const *)ptr - bytes); } gb_inline isize gb_pointer_diff (void const *begin, void const *end) { return cast(isize)(cast(u8 const *)end - cast(u8 const *)begin); } -gb_inline void gb_zero_size(void *ptr, isize size) { gb_memset(ptr, 0, size); } +gb_inline void gb_zero_size(void *ptr, isize size) { memset(ptr, 0, size); } #if defined(_MSC_VER) && !defined(__clang__) @@ -3009,6 +3026,10 @@ gb_inline u32 gb_thread_current_id(void) { thread_id = gettid(); #elif defined(GB_SYSTEM_HAIKU) thread_id = find_thread(NULL); +#elif defined(GB_SYSTEM_FREEBSD) + thread_id = pthread_getthreadid_np(); +#elif defined(GB_SYSTEM_NETBSD) + thread_id = (u32)_lwp_self(); #else #error Unsupported architecture for gb_thread_current_id() #endif @@ -3267,6 +3288,31 @@ isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { GB_ASSERT(0 <= core && core < a->core_count); return a->threads_per_core; } + +#elif defined(GB_SYSTEM_NETBSD) +#include + +void gb_affinity_init(gbAffinity *a) { + a->core_count = sysconf(_SC_NPROCESSORS_ONLN); + a->threads_per_core = 1; + a->is_accurate = a->core_count > 0; + a->core_count = a->is_accurate ? a->core_count : 1; + a->thread_count = a->core_count; +} + +void gb_affinity_destroy(gbAffinity *a) { + gb_unused(a); +} + +b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) { + return true; +} + +isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { + GB_ASSERT(0 <= core && core < a->core_count); + return a->threads_per_core; +} + #elif defined(GB_SYSTEM_HAIKU) #include @@ -6218,11 +6264,12 @@ gb_no_inline isize gb_snprintf_va(char *text, isize max_len, char const *fmt, va #elif defined(__aarch64__) gb_inline u64 gb_rdtsc(void) { int64_t virtual_timer_value; - asm volatile("mrs %0, cntvct_el0" : "=r"(virtual_timer_value)); - return virtual_timer_value; + asm volatile("mrs %0, cntvct_el0" : "=r"(virtual_timer_value)); + return virtual_timer_value; } #else -#error "gb_rdtsc not supported" +#warning "gb_rdtsc not supported" + gb_inline u64 gb_rdtsc(void) { return 0; } #endif #if defined(GB_SYSTEM_WINDOWS) diff --git a/src/linker.cpp b/src/linker.cpp index 498a96c5f..046e72d0e 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -13,6 +13,7 @@ struct LinkerData { }; gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, ...); +gb_internal bool system_exec_command_line_app_output(char const *command, gbString *output); #if defined(GB_SYSTEM_OSX) gb_internal void linker_enable_system_library_linking(LinkerData *ld) { @@ -69,15 +70,68 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (is_arch_wasm()) { timings_start_section(timings, str_lit("wasm-ld")); + gbString lib_str = gb_string_make(heap_allocator(), ""); + + gbString extra_orca_flags = gb_string_make(temporary_allocator(), ""); + + gbString inputs = gb_string_make(temporary_allocator(), ""); + inputs = gb_string_append_fmt(inputs, "\"%.*s.o\"", LIT(output_filename)); + + + for (Entity *e : gen->foreign_libraries) { + GB_ASSERT(e->kind == Entity_LibraryName); + // NOTE(bill): Add these before the linking values + String extra_linker_flags = string_trim_whitespace(e->LibraryName.extra_linker_flags); + if (extra_linker_flags.len != 0) { + lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(extra_linker_flags)); + } + + for_array(i, e->LibraryName.paths) { + String lib = e->LibraryName.paths[i]; + + if (lib.len == 0) { + continue; + } + + if (!string_ends_with(lib, str_lit(".o"))) { + continue; + } + + inputs = gb_string_append_fmt(inputs, " \"%.*s\"", LIT(lib)); + } + } + + if (build_context.metrics.os == TargetOs_orca) { + gbString orca_sdk_path = gb_string_make(temporary_allocator(), ""); + if (!system_exec_command_line_app_output("orca sdk-path", &orca_sdk_path)) { + gb_printf_err("executing `orca sdk-path` failed, make sure Orca is installed and added to your path\n"); + return 1; + } + if (gb_string_length(orca_sdk_path) == 0) { + gb_printf_err("executing `orca sdk-path` did not produce output\n"); + return 1; + } + inputs = gb_string_append_fmt(inputs, " \"%s/orca-libc/lib/crt1.o\" \"%s/orca-libc/lib/libc.o\"", orca_sdk_path, orca_sdk_path); + + extra_orca_flags = gb_string_append_fmt(extra_orca_flags, " -L \"%s/bin\" -lorca_wasm --export-dynamic", orca_sdk_path); + } + + #if defined(GB_SYSTEM_WINDOWS) result = system_exec_command_line_app("wasm-ld", - "\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s", + "\"%.*s\\bin\\wasm-ld\" %s -o \"%.*s\" %.*s %.*s %s %s", LIT(build_context.ODIN_ROOT), - LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); + inputs, LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags), + lib_str, + extra_orca_flags); #else result = system_exec_command_line_app("wasm-ld", - "wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s", - LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); + "wasm-ld %s -o \"%.*s\" %.*s %.*s %s %s", + inputs, LIT(output_filename), + LIT(build_context.link_flags), + LIT(build_context.extra_linker_flags), + lib_str, + extra_orca_flags); #endif return result; } @@ -167,8 +221,22 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (has_asm_extension(lib)) { if (!string_set_update(&asm_files, lib)) { - String asm_file = asm_files.entries[i].value; - String obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".obj")); + String asm_file = lib; + String obj_file = {}; + String temp_dir = temporary_directory(temporary_allocator()); + if (temp_dir.len != 0) { + String filename = filename_without_directory(asm_file); + + gbString str = gb_string_make(heap_allocator(), ""); + str = gb_string_append_length(str, temp_dir.text, temp_dir.len); + str = gb_string_appendc(str, "/"); + str = gb_string_append_length(str, filename.text, filename.len); + str = gb_string_append_fmt(str, "-%p.obj", asm_file.text); + obj_file = make_string_c(str); + } else { + obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".obj")); + } + String obj_format = str_lit("win64"); #if defined(GB_ARCH_32_BIT) obj_format = str_lit("win32"); @@ -212,10 +280,12 @@ gb_internal i32 linker_stage(LinkerData *gen) { link_settings = gb_string_append_fmt(link_settings, " /PDB:\"%.*s\"", LIT(pdb_path)); } - if (build_context.no_crt) { - link_settings = gb_string_append_fmt(link_settings, " /nodefaultlib"); - } else { - link_settings = gb_string_append_fmt(link_settings, " /defaultlib:libcmt"); + if (build_context.build_mode != BuildMode_StaticLibrary) { + if (build_context.no_crt) { + link_settings = gb_string_append_fmt(link_settings, " /nodefaultlib"); + } else { + link_settings = gb_string_append_fmt(link_settings, " /defaultlib:libcmt"); + } } if (build_context.ODIN_DEBUG) { @@ -235,42 +305,57 @@ gb_internal i32 linker_stage(LinkerData *gen) { defer (gb_free(heap_allocator(), windows_sdk_bin_path.text)); if (!build_context.use_lld) { // msvc - String res_path = {}; + String res_path = quote_path(heap_allocator(), build_context.build_paths[BuildPath_RES]); + String rc_path = quote_path(heap_allocator(), build_context.build_paths[BuildPath_RC]); defer (gb_free(heap_allocator(), res_path.text)); + defer (gb_free(heap_allocator(), rc_path.text)); + if (build_context.has_resource) { - String temp_res_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RES]); - res_path = concatenate3_strings(heap_allocator(), str_lit("\""), temp_res_path, str_lit("\"")); - gb_free(heap_allocator(), temp_res_path.text); + if (build_context.build_paths[BuildPath_RC].basename == "") { + debugf("Using precompiled resource %.*s\n", LIT(res_path)); + } else { + debugf("Compiling resource %.*s\n", LIT(res_path)); - String rc_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RC]); - defer (gb_free(heap_allocator(), rc_path.text)); + result = system_exec_command_line_app("msvc-link", + "\"%.*src.exe\" /nologo /fo %.*s %.*s", + LIT(windows_sdk_bin_path), + LIT(res_path), + LIT(rc_path) + ); - result = system_exec_command_line_app("msvc-link", - "\"%.*src.exe\" /nologo /fo \"%.*s\" \"%.*s\"", - LIT(windows_sdk_bin_path), - LIT(res_path), - LIT(rc_path) - ); - - if (result) { - return result; + if (result) { + return result; + } } + } else { + res_path = {}; } + String linker_name = str_lit("link.exe"); switch (build_context.build_mode) { case BuildMode_Executable: link_settings = gb_string_append_fmt(link_settings, " /NOIMPLIB /NOEXP"); break; } + switch (build_context.build_mode) { + case BuildMode_StaticLibrary: + linker_name = str_lit("lib.exe"); + break; + default: + link_settings = gb_string_append_fmt(link_settings, " /incremental:no /opt:ref"); + break; + } + + result = system_exec_command_line_app("msvc-link", - "\"%.*slink.exe\" %s %.*s -OUT:\"%.*s\" %s " - "/nologo /incremental:no /opt:ref /subsystem:%.*s " + "\"%.*s%.*s\" %s %.*s -OUT:\"%.*s\" %s " + "/nologo /subsystem:%.*s " "%.*s " "%.*s " "%s " "", - LIT(vs_exe_path), object_files, LIT(res_path), LIT(output_filename), + LIT(vs_exe_path), LIT(linker_name), object_files, LIT(res_path), LIT(output_filename), link_settings, LIT(build_context.ODIN_WINDOWS_SUBSYSTEM), LIT(build_context.link_flags), @@ -342,7 +427,22 @@ gb_internal i32 linker_stage(LinkerData *gen) { continue; // already handled } String asm_file = lib; - String obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".o")); + String obj_file = {}; + + String temp_dir = temporary_directory(temporary_allocator()); + if (temp_dir.len != 0) { + String filename = filename_without_directory(asm_file); + + gbString str = gb_string_make(heap_allocator(), ""); + str = gb_string_append_length(str, temp_dir.text, temp_dir.len); + str = gb_string_appendc(str, "/"); + str = gb_string_append_length(str, filename.text, filename.len); + str = gb_string_append_fmt(str, "-%p.o", asm_file.text); + obj_file = make_string_c(str); + } else { + obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".o")); + } + String obj_format; #if defined(GB_ARCH_64_BIT) if (is_osx) { @@ -432,7 +532,7 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o"))) { // static libs and object files, absolute full path relative to the file in which the lib was imported from lib_str = gb_string_append_fmt(lib_str, " -l:\"%.*s\" ", LIT(lib)); - } else if (string_ends_with(lib, str_lit(".so"))) { + } else if (string_ends_with(lib, str_lit(".so")) || string_contains_string(lib, str_lit(".so."))) { // dynamic lib, relative path to executable // NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible // at runtime to the executable @@ -458,6 +558,10 @@ gb_internal i32 linker_stage(LinkerData *gen) { link_settings = gb_string_append_fmt(link_settings, "-nostdlib "); } + if (build_context.build_mode == BuildMode_StaticLibrary) { + compiler_error("TODO(bill): -build-mode:static on non-windows targets"); + } + // NOTE(dweiler): We use clang as a frontend for the linker as there are // other runtime and compiler support libraries that need to be linked in // very specific orders such as libgcc_s, ld-linux-so, unwind, etc. @@ -531,9 +635,16 @@ gb_internal i32 linker_stage(LinkerData *gen) { } } - gbString link_command_line = gb_string_make(heap_allocator(), "clang -Wno-unused-command-line-argument "); + // 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)); + link_command_line = gb_string_appendc(link_command_line, " -Wno-unused-command-line-argument "); link_command_line = gb_string_appendc(link_command_line, object_files); link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s\" ", LIT(output_filename)); link_command_line = gb_string_append_fmt(link_command_line, " %s ", platform_lib_str); diff --git a/src/llvm-c/Analysis.h b/src/llvm-c/Analysis.h index 270b145a4..6b93b5c3d 100644 --- a/src/llvm-c/Analysis.h +++ b/src/llvm-c/Analysis.h @@ -19,8 +19,8 @@ #ifndef LLVM_C_ANALYSIS_H #define LLVM_C_ANALYSIS_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/BitReader.h b/src/llvm-c/BitReader.h index 088107468..725f3fa84 100644 --- a/src/llvm-c/BitReader.h +++ b/src/llvm-c/BitReader.h @@ -19,8 +19,8 @@ #ifndef LLVM_C_BITREADER_H #define LLVM_C_BITREADER_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/BitWriter.h b/src/llvm-c/BitWriter.h index ea84b6593..ba4a61afc 100644 --- a/src/llvm-c/BitWriter.h +++ b/src/llvm-c/BitWriter.h @@ -19,8 +19,8 @@ #ifndef LLVM_C_BITWRITER_H #define LLVM_C_BITWRITER_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/Comdat.h b/src/llvm-c/Comdat.h index 8002bc058..30df20799 100644 --- a/src/llvm-c/Comdat.h +++ b/src/llvm-c/Comdat.h @@ -14,8 +14,8 @@ #ifndef LLVM_C_COMDAT_H #define LLVM_C_COMDAT_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/Config/AsmParsers.def b/src/llvm-c/Config/AsmParsers.def index 7aaab6e50..a641f395b 100644 --- a/src/llvm-c/Config/AsmParsers.def +++ b/src/llvm-c/Config/AsmParsers.def @@ -27,18 +27,20 @@ LLVM_ASM_PARSER(AArch64) LLVM_ASM_PARSER(AMDGPU) LLVM_ASM_PARSER(ARM) +LLVM_ASM_PARSER(AVR) LLVM_ASM_PARSER(BPF) LLVM_ASM_PARSER(Hexagon) LLVM_ASM_PARSER(Lanai) +LLVM_ASM_PARSER(LoongArch) LLVM_ASM_PARSER(Mips) LLVM_ASM_PARSER(MSP430) LLVM_ASM_PARSER(PowerPC) LLVM_ASM_PARSER(RISCV) LLVM_ASM_PARSER(Sparc) LLVM_ASM_PARSER(SystemZ) +LLVM_ASM_PARSER(VE) LLVM_ASM_PARSER(WebAssembly) LLVM_ASM_PARSER(X86) -LLVM_ASM_PARSER(AVR) #undef LLVM_ASM_PARSER diff --git a/src/llvm-c/Config/AsmPrinters.def b/src/llvm-c/Config/AsmPrinters.def index 3ecc3644f..c463c3e89 100644 --- a/src/llvm-c/Config/AsmPrinters.def +++ b/src/llvm-c/Config/AsmPrinters.def @@ -27,9 +27,11 @@ LLVM_ASM_PRINTER(AArch64) LLVM_ASM_PRINTER(AMDGPU) LLVM_ASM_PRINTER(ARM) +LLVM_ASM_PRINTER(AVR) LLVM_ASM_PRINTER(BPF) LLVM_ASM_PRINTER(Hexagon) LLVM_ASM_PRINTER(Lanai) +LLVM_ASM_PRINTER(LoongArch) LLVM_ASM_PRINTER(Mips) LLVM_ASM_PRINTER(MSP430) LLVM_ASM_PRINTER(NVPTX) @@ -37,10 +39,10 @@ LLVM_ASM_PRINTER(PowerPC) LLVM_ASM_PRINTER(RISCV) LLVM_ASM_PRINTER(Sparc) LLVM_ASM_PRINTER(SystemZ) +LLVM_ASM_PRINTER(VE) LLVM_ASM_PRINTER(WebAssembly) LLVM_ASM_PRINTER(X86) LLVM_ASM_PRINTER(XCore) -LLVM_ASM_PRINTER(AVR) #undef LLVM_ASM_PRINTER diff --git a/src/llvm-c/Config/Disassemblers.def b/src/llvm-c/Config/Disassemblers.def index 4485af241..dce865414 100644 --- a/src/llvm-c/Config/Disassemblers.def +++ b/src/llvm-c/Config/Disassemblers.def @@ -27,19 +27,21 @@ LLVM_DISASSEMBLER(AArch64) LLVM_DISASSEMBLER(AMDGPU) LLVM_DISASSEMBLER(ARM) +LLVM_DISASSEMBLER(AVR) LLVM_DISASSEMBLER(BPF) LLVM_DISASSEMBLER(Hexagon) LLVM_DISASSEMBLER(Lanai) +LLVM_DISASSEMBLER(LoongArch) LLVM_DISASSEMBLER(Mips) LLVM_DISASSEMBLER(MSP430) LLVM_DISASSEMBLER(PowerPC) LLVM_DISASSEMBLER(RISCV) LLVM_DISASSEMBLER(Sparc) LLVM_DISASSEMBLER(SystemZ) +LLVM_DISASSEMBLER(VE) LLVM_DISASSEMBLER(WebAssembly) LLVM_DISASSEMBLER(X86) LLVM_DISASSEMBLER(XCore) -LLVM_DISASSEMBLER(AVR) #undef LLVM_DISASSEMBLER diff --git a/src/llvm-c/Config/TargetExegesis.def b/src/llvm-c/Config/TargetExegesis.def new file mode 100644 index 000000000..d4d3f99a7 --- /dev/null +++ b/src/llvm-c/Config/TargetExegesis.def @@ -0,0 +1,33 @@ +/*===----- llvm/Config/TargetExegesis.def - LLVM Target Exegesis-*- C++ -*-===*\ +|* *| +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM *| +|* Exceptions. *| +|* See https://llvm.org/LICENSE.txt for license information. *| +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception *| +|* *| +|*===----------------------------------------------------------------------===*| +|* *| +|* This file enumerates all of the target's of llvm-exegesis *| +|* supported by this build of LLVM. Clients of this file should define *| +|* the LLVM_EXEGISIS macro to be a function-like macro with a *| +|* single parameter (the name of the target whose assembly can be *| +|* generated); including this file will then enumerate all of the *| +|* targets with target llvm-exegsis support. *| +|* *| +|* The set of targets supported by LLVM is generated at configuration *| +|* time, at which point this header is generated. Do not modify this *| +|* header directly. *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef LLVM_EXEGESIS +# error Please define the macro LLVM_EXEGESIS(TargetName) +#endif + +LLVM_EXEGESIS(AArch64) +LLVM_EXEGESIS(Mips) +LLVM_EXEGESIS(PowerPC) +LLVM_EXEGESIS(X86) + + +#undef LLVM_EXEGESIS diff --git a/src/llvm-c/Config/TargetMCAs.def b/src/llvm-c/Config/TargetMCAs.def new file mode 100644 index 000000000..1aefdbc25 --- /dev/null +++ b/src/llvm-c/Config/TargetMCAs.def @@ -0,0 +1,32 @@ +/*===------ llvm/Config/TargetMCAs.def - LLVM Target MCAs -------*- C++ -*-===*\ +|* *| +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM *| +|* Exceptions. *| +|* See https://llvm.org/LICENSE.txt for license information. *| +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception *| +|* *| +|*===----------------------------------------------------------------------===*| +|* *| +|* This file enumerates all of the target MCAs *| +|* supported by this build of LLVM. Clients of this file should define *| +|* the LLVM_TARGETMCA macro to be a function-like macro with a *| +|* single parameter (the name of the target whose assembly can be *| +|* generated); including this file will then enumerate all of the *| +|* targets with target MCAs. *| +|* *| +|* The set of targets supported by LLVM is generated at configuration *| +|* time, at which point this header is generated. Do not modify this *| +|* header directly. *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef LLVM_TARGETMCA +# error Please define the macro LLVM_TARGETMCA(TargetName) +#endif + +LLVM_TARGETMCA(AMDGPU) +LLVM_TARGETMCA(RISCV) +LLVM_TARGETMCA(X86) + + +#undef LLVM_TARGETMCA diff --git a/src/llvm-c/Config/Targets.def b/src/llvm-c/Config/Targets.def index 4962add96..250aac5c9 100644 --- a/src/llvm-c/Config/Targets.def +++ b/src/llvm-c/Config/Targets.def @@ -26,9 +26,11 @@ LLVM_TARGET(AArch64) LLVM_TARGET(AMDGPU) LLVM_TARGET(ARM) +LLVM_TARGET(AVR) LLVM_TARGET(BPF) LLVM_TARGET(Hexagon) LLVM_TARGET(Lanai) +LLVM_TARGET(LoongArch) LLVM_TARGET(Mips) LLVM_TARGET(MSP430) LLVM_TARGET(NVPTX) @@ -36,10 +38,10 @@ LLVM_TARGET(PowerPC) LLVM_TARGET(RISCV) LLVM_TARGET(Sparc) LLVM_TARGET(SystemZ) +LLVM_TARGET(VE) LLVM_TARGET(WebAssembly) LLVM_TARGET(X86) LLVM_TARGET(XCore) -LLVM_TARGET(AVR) #undef LLVM_TARGET diff --git a/src/llvm-c/Config/abi-breaking.h b/src/llvm-c/Config/abi-breaking.h index a09cffa7e..c501cc354 100644 --- a/src/llvm-c/Config/abi-breaking.h +++ b/src/llvm-c/Config/abi-breaking.h @@ -1,4 +1,4 @@ -/*===------- llvm-c/Config//abi-breaking.h - llvm configuration -------*- C -*-===*/ +/*===------- llvm/Config/abi-breaking.h - llvm configuration -------*- C -*-===*/ /* */ /* Part of the LLVM Project, under the Apache License v2.0 with LLVM */ /* Exceptions. */ @@ -20,7 +20,7 @@ /* Allow selectively disabling link-time mismatch checking so that header-only ADT content from LLVM can be used without linking libSupport. */ -#if !LLVM_DISABLE_ABI_BREAKING_CHECKS_ENFORCING +#if !defined(LLVM_DISABLE_ABI_BREAKING_CHECKS_ENFORCING) || !LLVM_DISABLE_ABI_BREAKING_CHECKS_ENFORCING // ABI_BREAKING_CHECKS protection: provides link-time failure when clients build // mismatch with LLVM diff --git a/src/llvm-c/Config/llvm-config.h b/src/llvm-c/Config/llvm-config.h index 331d05093..e4edb83c5 100644 --- a/src/llvm-c/Config/llvm-config.h +++ b/src/llvm-c/Config/llvm-config.h @@ -1,4 +1,4 @@ -/*===------- llvm-c/Config//llvm-config.h - llvm configuration -------*- C -*-===*/ +/*===------- llvm/Config/llvm-config.h - llvm configuration -------*- C -*-===*/ /* */ /* Part of the LLVM Project, under the Apache License v2.0 with LLVM */ /* Exceptions. */ @@ -17,10 +17,8 @@ /* Define if LLVM_ENABLE_DUMP is enabled */ /* #undef LLVM_ENABLE_DUMP */ -/* Define if we link Polly to the tools */ -/* #undef LINK_POLLY_INTO_TOOLS */ - /* Target triple LLVM will generate code for by default */ +/* Doesn't use `cmakedefine` because it is allowed to be empty. */ #define LLVM_DEFAULT_TARGET_TRIPLE "x86_64-pc-windows-msvc" /* Define if threads enabled */ @@ -53,6 +51,84 @@ /* LLVM name for the native target MC init function, if available */ #define LLVM_NATIVE_TARGETMC LLVMInitializeX86TargetMC +/* LLVM name for the native target MCA init function, if available */ +/* #undef LLVM_NATIVE_TARGETMCA */ + +/* Define if the AArch64 target is built in */ +#define LLVM_HAS_AARCH64_TARGET 1 + +/* Define if the AMDGPU target is built in */ +#define LLVM_HAS_AMDGPU_TARGET 1 + +/* Define if the ARC target is built in */ +#define LLVM_HAS_ARC_TARGET 0 + +/* Define if the ARM target is built in */ +#define LLVM_HAS_ARM_TARGET 1 + +/* Define if the AVR target is built in */ +#define LLVM_HAS_AVR_TARGET 1 + +/* Define if the BPF target is built in */ +#define LLVM_HAS_BPF_TARGET 1 + +/* Define if the CSKY target is built in */ +#define LLVM_HAS_CSKY_TARGET 0 + +/* Define if the DirectX target is built in */ +#define LLVM_HAS_DIRECTX_TARGET 0 + +/* Define if the Hexagon target is built in */ +#define LLVM_HAS_HEXAGON_TARGET 1 + +/* Define if the Lanai target is built in */ +#define LLVM_HAS_LANAI_TARGET 1 + +/* Define if the LoongArch target is built in */ +#define LLVM_HAS_LOONGARCH_TARGET 1 + +/* Define if the M68k target is built in */ +#define LLVM_HAS_M68K_TARGET 0 + +/* Define if the Mips target is built in */ +#define LLVM_HAS_MIPS_TARGET 1 + +/* Define if the MSP430 target is built in */ +#define LLVM_HAS_MSP430_TARGET 1 + +/* Define if the NVPTX target is built in */ +#define LLVM_HAS_NVPTX_TARGET 1 + +/* Define if the PowerPC target is built in */ +#define LLVM_HAS_POWERPC_TARGET 1 + +/* Define if the RISCV target is built in */ +#define LLVM_HAS_RISCV_TARGET 1 + +/* Define if the Sparc target is built in */ +#define LLVM_HAS_SPARC_TARGET 1 + +/* Define if the SPIRV target is built in */ +#define LLVM_HAS_SPIRV_TARGET 0 + +/* Define if the SystemZ target is built in */ +#define LLVM_HAS_SYSTEMZ_TARGET 1 + +/* Define if the VE target is built in */ +#define LLVM_HAS_VE_TARGET 1 + +/* Define if the WebAssembly target is built in */ +#define LLVM_HAS_WEBASSEMBLY_TARGET 1 + +/* Define if the X86 target is built in */ +#define LLVM_HAS_X86_TARGET 1 + +/* Define if the XCore target is built in */ +#define LLVM_HAS_XCORE_TARGET 1 + +/* Define if the Xtensa target is built in */ +#define LLVM_HAS_XTENSA_TARGET 0 + /* Define if this is Unixish platform */ /* #undef LLVM_ON_UNIX */ @@ -66,20 +142,60 @@ #define LLVM_USE_PERF 0 /* Major version of the LLVM API */ -#define LLVM_VERSION_MAJOR 17 +#define LLVM_VERSION_MAJOR 18 /* Minor version of the LLVM API */ -#define LLVM_VERSION_MINOR 0 +#define LLVM_VERSION_MINOR 1 /* Patch version of the LLVM API */ -#define LLVM_VERSION_PATCH 1 +#define LLVM_VERSION_PATCH 8 /* LLVM version string */ -#define LLVM_VERSION_STRING "17.0.1" +#define LLVM_VERSION_STRING "18.1.8" /* Whether LLVM records statistics for use with GetStatistics(), * PrintStatistics() or PrintStatisticsJSON() */ #define LLVM_FORCE_ENABLE_STATS 0 +/* Define if we have z3 and want to build it */ +/* #undef LLVM_WITH_Z3 */ + +/* Define if we have curl and want to use it */ +/* #undef LLVM_ENABLE_CURL */ + +/* Define if we have cpp-httplib and want to use it */ +/* #undef LLVM_ENABLE_HTTPLIB */ + +/* Define if zlib compression is available */ +#define LLVM_ENABLE_ZLIB 0 + +/* Define if zstd compression is available */ +#define LLVM_ENABLE_ZSTD 0 + +/* Define if LLVM is using tflite */ +/* #undef LLVM_HAVE_TFLITE */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYSEXITS_H */ + +/* Define if building libLLVM shared library */ +/* #undef LLVM_BUILD_LLVM_DYLIB */ + +/* Define if building LLVM with BUILD_SHARED_LIBS */ +/* #undef LLVM_BUILD_SHARED_LIBS */ + +/* Define if building LLVM with LLVM_FORCE_USE_OLD_TOOLCHAIN_LIBS */ +/* #undef LLVM_FORCE_USE_OLD_TOOLCHAIN */ + +/* Define if llvm_unreachable should be optimized with undefined behavior + * in non assert builds */ +#define LLVM_UNREACHABLE_OPTIMIZE 1 + +/* Define to 1 if you have the DIA SDK installed, and to 0 if you don't. */ +#define LLVM_ENABLE_DIA_SDK 1 + +/* Define if plugins enabled */ +/* #undef LLVM_ENABLE_PLUGINS */ + #endif diff --git a/src/llvm-c/Core.h b/src/llvm-c/Core.h index fbba8ca42..25b8248fd 100644 --- a/src/llvm-c/Core.h +++ b/src/llvm-c/Core.h @@ -15,11 +15,11 @@ #ifndef LLVM_C_CORE_H #define LLVM_C_CORE_H -#include "llvm-c/Deprecated.h" -#include "llvm-c/ErrorHandling.h" -#include "llvm-c/ExternC.h" +#include "Deprecated.h" +#include "ErrorHandling.h" +#include "ExternC.h" -#include "llvm-c/Types.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN @@ -216,7 +216,6 @@ typedef enum { LLVMColdCallConv = 9, LLVMGHCCallConv = 10, LLVMHiPECallConv = 11, - LLVMWebKitJSCallConv = 12, LLVMAnyRegCallConv = 13, LLVMPreserveMostCallConv = 14, LLVMPreserveAllCallConv = 15, @@ -468,8 +467,45 @@ enum { LLVMAttributeFunctionIndex = -1, }; +/** + * Tail call kind for LLVMSetTailCallKind and LLVMGetTailCallKind. + * + * Note that 'musttail' implies 'tail'. + * + * @see CallInst::TailCallKind + */ +typedef enum { + LLVMTailCallKindNone = 0, + LLVMTailCallKindTail = 1, + LLVMTailCallKindMustTail = 2, + LLVMTailCallKindNoTail = 3, +} LLVMTailCallKind; + typedef unsigned LLVMAttributeIndex; +enum { + LLVMFastMathAllowReassoc = (1 << 0), + LLVMFastMathNoNaNs = (1 << 1), + LLVMFastMathNoInfs = (1 << 2), + LLVMFastMathNoSignedZeros = (1 << 3), + LLVMFastMathAllowReciprocal = (1 << 4), + LLVMFastMathAllowContract = (1 << 5), + LLVMFastMathApproxFunc = (1 << 6), + LLVMFastMathNone = 0, + LLVMFastMathAll = LLVMFastMathAllowReassoc | LLVMFastMathNoNaNs | + LLVMFastMathNoInfs | LLVMFastMathNoSignedZeros | + LLVMFastMathAllowReciprocal | LLVMFastMathAllowContract | + LLVMFastMathApproxFunc, +}; + +/** + * Flags to indicate what fast-math-style optimizations are allowed + * on operations. + * + * See https://llvm.org/docs/LangRef.html#fast-math-flags + */ +typedef unsigned LLVMFastMathFlags; + /** * @} */ @@ -890,12 +926,58 @@ void LLVMAppendModuleInlineAsm(LLVMModuleRef M, const char *Asm, size_t Len); * * @see InlineAsm::get() */ -LLVMValueRef LLVMGetInlineAsm(LLVMTypeRef Ty, char *AsmString, - size_t AsmStringSize, char *Constraints, +LLVMValueRef LLVMGetInlineAsm(LLVMTypeRef Ty, const char *AsmString, + size_t AsmStringSize, const char *Constraints, size_t ConstraintsSize, LLVMBool HasSideEffects, LLVMBool IsAlignStack, LLVMInlineAsmDialect Dialect, LLVMBool CanThrow); +/** + * Get the template string used for an inline assembly snippet + * + */ +const char *LLVMGetInlineAsmAsmString(LLVMValueRef InlineAsmVal, size_t *Len); + +/** + * Get the raw constraint string for an inline assembly snippet + * + */ +const char *LLVMGetInlineAsmConstraintString(LLVMValueRef InlineAsmVal, + size_t *Len); + +/** + * Get the dialect used by the inline asm snippet + * + */ +LLVMInlineAsmDialect LLVMGetInlineAsmDialect(LLVMValueRef InlineAsmVal); + +/** + * Get the function type of the inline assembly snippet. The same type that + * was passed into LLVMGetInlineAsm originally + * + * @see LLVMGetInlineAsm + * + */ +LLVMTypeRef LLVMGetInlineAsmFunctionType(LLVMValueRef InlineAsmVal); + +/** + * Get if the inline asm snippet has side effects + * + */ +LLVMBool LLVMGetInlineAsmHasSideEffects(LLVMValueRef InlineAsmVal); + +/** + * Get if the inline asm snippet needs an aligned stack + * + */ +LLVMBool LLVMGetInlineAsmNeedsAlignedStack(LLVMValueRef InlineAsmVal); + +/** + * Get if the inline asm snippet may unwind the stack + * + */ +LLVMBool LLVMGetInlineAsmCanUnwind(LLVMValueRef InlineAsmVal); + /** * Obtain the context to which this module is associated. * @@ -2216,45 +2298,26 @@ LLVMValueRef LLVMConstNUWSub(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant) LLVMValueRef LLVMConstMul(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); LLVMValueRef LLVMConstNSWMul(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); LLVMValueRef LLVMConstNUWMul(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); -LLVMValueRef LLVMConstAnd(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); -LLVMValueRef LLVMConstOr(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); LLVMValueRef LLVMConstXor(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); LLVMValueRef LLVMConstICmp(LLVMIntPredicate Predicate, LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); LLVMValueRef LLVMConstFCmp(LLVMRealPredicate Predicate, LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); LLVMValueRef LLVMConstShl(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); -LLVMValueRef LLVMConstLShr(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); -LLVMValueRef LLVMConstAShr(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); LLVMValueRef LLVMConstGEP2(LLVMTypeRef Ty, LLVMValueRef ConstantVal, LLVMValueRef *ConstantIndices, unsigned NumIndices); LLVMValueRef LLVMConstInBoundsGEP2(LLVMTypeRef Ty, LLVMValueRef ConstantVal, LLVMValueRef *ConstantIndices, unsigned NumIndices); LLVMValueRef LLVMConstTrunc(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstSExt(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstZExt(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstFPTrunc(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstFPExt(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstUIToFP(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstSIToFP(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstFPToUI(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstFPToSI(LLVMValueRef ConstantVal, LLVMTypeRef ToType); LLVMValueRef LLVMConstPtrToInt(LLVMValueRef ConstantVal, LLVMTypeRef ToType); LLVMValueRef LLVMConstIntToPtr(LLVMValueRef ConstantVal, LLVMTypeRef ToType); LLVMValueRef LLVMConstBitCast(LLVMValueRef ConstantVal, LLVMTypeRef ToType); LLVMValueRef LLVMConstAddrSpaceCast(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstZExtOrBitCast(LLVMValueRef ConstantVal, - LLVMTypeRef ToType); -LLVMValueRef LLVMConstSExtOrBitCast(LLVMValueRef ConstantVal, - LLVMTypeRef ToType); LLVMValueRef LLVMConstTruncOrBitCast(LLVMValueRef ConstantVal, LLVMTypeRef ToType); LLVMValueRef LLVMConstPointerCast(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstIntCast(LLVMValueRef ConstantVal, LLVMTypeRef ToType, - LLVMBool isSigned); -LLVMValueRef LLVMConstFPCast(LLVMValueRef ConstantVal, LLVMTypeRef ToType); LLVMValueRef LLVMConstExtractElement(LLVMValueRef VectorConstant, LLVMValueRef IndexConstant); LLVMValueRef LLVMConstInsertElement(LLVMValueRef VectorConstant, @@ -2960,6 +3023,74 @@ LLVMValueRef LLVMMDNodeInContext(LLVMContextRef C, LLVMValueRef *Vals, /** Deprecated: Use LLVMMDNodeInContext2 instead. */ LLVMValueRef LLVMMDNode(LLVMValueRef *Vals, unsigned Count); +/** + * @} + */ + +/** + * @defgroup LLVMCCoreOperandBundle Operand Bundles + * + * Functions in this group operate on LLVMOperandBundleRef instances that + * correspond to llvm::OperandBundleDef instances. + * + * @see llvm::OperandBundleDef + * + * @{ + */ + +/** + * Create a new operand bundle. + * + * Every invocation should be paired with LLVMDisposeOperandBundle() or memory + * will be leaked. + * + * @param Tag Tag name of the operand bundle + * @param TagLen Length of Tag + * @param Args Memory address of an array of bundle operands + * @param NumArgs Length of Args + */ +LLVMOperandBundleRef LLVMCreateOperandBundle(const char *Tag, size_t TagLen, + LLVMValueRef *Args, + unsigned NumArgs); + +/** + * Destroy an operand bundle. + * + * This must be called for every created operand bundle or memory will be + * leaked. + */ +void LLVMDisposeOperandBundle(LLVMOperandBundleRef Bundle); + +/** + * Obtain the tag of an operand bundle as a string. + * + * @param Bundle Operand bundle to obtain tag of. + * @param Len Out parameter which holds the length of the returned string. + * @return The tag name of Bundle. + * @see OperandBundleDef::getTag() + */ +const char *LLVMGetOperandBundleTag(LLVMOperandBundleRef Bundle, size_t *Len); + +/** + * Obtain the number of operands for an operand bundle. + * + * @param Bundle Operand bundle to obtain operand count of. + * @return The number of operands. + * @see OperandBundleDef::input_size() + */ +unsigned LLVMGetNumOperandBundleArgs(LLVMOperandBundleRef Bundle); + +/** + * Obtain the operand for an operand bundle at the given index. + * + * @param Bundle Operand bundle to obtain operand of. + * @param Index An operand index, must be less than + * LLVMGetNumOperandBundleArgs(). + * @return The operand. + */ +LLVMValueRef LLVMGetOperandBundleArgAtIndex(LLVMOperandBundleRef Bundle, + unsigned Index); + /** * @} */ @@ -3411,6 +3542,24 @@ LLVMTypeRef LLVMGetCalledFunctionType(LLVMValueRef C); */ LLVMValueRef LLVMGetCalledValue(LLVMValueRef Instr); +/** + * Obtain the number of operand bundles attached to this instruction. + * + * This only works on llvm::CallInst and llvm::InvokeInst instructions. + * + * @see llvm::CallBase::getNumOperandBundles() + */ +unsigned LLVMGetNumOperandBundles(LLVMValueRef C); + +/** + * Obtain the operand bundle attached to this instruction at the given index. + * Use LLVMDisposeOperandBundle to free the operand bundle. + * + * This only works on llvm::CallInst and llvm::InvokeInst instructions. + */ +LLVMOperandBundleRef LLVMGetOperandBundleAtIndex(LLVMValueRef C, + unsigned Index); + /** * Obtain whether a call instruction is a tail call. * @@ -3429,6 +3578,20 @@ LLVMBool LLVMIsTailCall(LLVMValueRef CallInst); */ void LLVMSetTailCall(LLVMValueRef CallInst, LLVMBool IsTailCall); +/** + * Obtain a tail call kind of the call instruction. + * + * @see llvm::CallInst::setTailCallKind() + */ +LLVMTailCallKind LLVMGetTailCallKind(LLVMValueRef CallInst); + +/** + * Set the call kind of the call instruction. + * + * @see llvm::CallInst::getTailCallKind() + */ +void LLVMSetTailCallKind(LLVMValueRef CallInst, LLVMTailCallKind kind); + /** * Return the normal destination basic block. * @@ -3761,6 +3924,10 @@ LLVMValueRef LLVMBuildInvoke2(LLVMBuilderRef, LLVMTypeRef Ty, LLVMValueRef Fn, LLVMValueRef *Args, unsigned NumArgs, LLVMBasicBlockRef Then, LLVMBasicBlockRef Catch, const char *Name); +LLVMValueRef LLVMBuildInvokeWithOperandBundles( + LLVMBuilderRef, LLVMTypeRef Ty, LLVMValueRef Fn, LLVMValueRef *Args, + unsigned NumArgs, LLVMBasicBlockRef Then, LLVMBasicBlockRef Catch, + LLVMOperandBundleRef *Bundles, unsigned NumBundles, const char *Name); LLVMValueRef LLVMBuildUnreachable(LLVMBuilderRef); /* Exception Handling */ @@ -3920,6 +4087,55 @@ void LLVMSetNSW(LLVMValueRef ArithInst, LLVMBool HasNSW); LLVMBool LLVMGetExact(LLVMValueRef DivOrShrInst); void LLVMSetExact(LLVMValueRef DivOrShrInst, LLVMBool IsExact); +/** + * Gets if the instruction has the non-negative flag set. + * Only valid for zext instructions. + */ +LLVMBool LLVMGetNNeg(LLVMValueRef NonNegInst); +/** + * Sets the non-negative flag for the instruction. + * Only valid for zext instructions. + */ +void LLVMSetNNeg(LLVMValueRef NonNegInst, LLVMBool IsNonNeg); + +/** + * Get the flags for which fast-math-style optimizations are allowed for this + * value. + * + * Only valid on floating point instructions. + * @see LLVMCanValueUseFastMathFlags + */ +LLVMFastMathFlags LLVMGetFastMathFlags(LLVMValueRef FPMathInst); + +/** + * Sets the flags for which fast-math-style optimizations are allowed for this + * value. + * + * Only valid on floating point instructions. + * @see LLVMCanValueUseFastMathFlags + */ +void LLVMSetFastMathFlags(LLVMValueRef FPMathInst, LLVMFastMathFlags FMF); + +/** + * Check if a given value can potentially have fast math flags. + * + * Will return true for floating point arithmetic instructions, and for select, + * phi, and call instructions whose type is a floating point type, or a vector + * or array thereof. See https://llvm.org/docs/LangRef.html#fast-math-flags + */ +LLVMBool LLVMCanValueUseFastMathFlags(LLVMValueRef Inst); + +/** + * Gets whether the instruction has the disjoint flag set. + * Only valid for or instructions. + */ +LLVMBool LLVMGetIsDisjoint(LLVMValueRef Inst); +/** + * Sets the disjoint flag for the instruction. + * Only valid for or instructions. + */ +void LLVMSetIsDisjoint(LLVMValueRef Inst, LLVMBool IsDisjoint); + /* Memory */ LLVMValueRef LLVMBuildMalloc(LLVMBuilderRef, LLVMTypeRef Ty, const char *Name); LLVMValueRef LLVMBuildArrayMalloc(LLVMBuilderRef, LLVMTypeRef Ty, @@ -4045,6 +4261,11 @@ LLVMValueRef LLVMBuildPhi(LLVMBuilderRef, LLVMTypeRef Ty, const char *Name); LLVMValueRef LLVMBuildCall2(LLVMBuilderRef, LLVMTypeRef, LLVMValueRef Fn, LLVMValueRef *Args, unsigned NumArgs, const char *Name); +LLVMValueRef +LLVMBuildCallWithOperandBundles(LLVMBuilderRef, LLVMTypeRef, LLVMValueRef Fn, + LLVMValueRef *Args, unsigned NumArgs, + LLVMOperandBundleRef *Bundles, + unsigned NumBundles, const char *Name); LLVMValueRef LLVMBuildSelect(LLVMBuilderRef, LLVMValueRef If, LLVMValueRef Then, LLVMValueRef Else, const char *Name); diff --git a/src/llvm-c/DebugInfo.h b/src/llvm-c/DebugInfo.h index 592429470..93bd9e2ad 100644 --- a/src/llvm-c/DebugInfo.h +++ b/src/llvm-c/DebugInfo.h @@ -16,8 +16,8 @@ #ifndef LLVM_C_DEBUGINFO_H #define LLVM_C_DEBUGINFO_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/Disassembler.h b/src/llvm-c/Disassembler.h index b1cb35da6..e6642f4ed 100644 --- a/src/llvm-c/Disassembler.h +++ b/src/llvm-c/Disassembler.h @@ -15,8 +15,8 @@ #ifndef LLVM_C_DISASSEMBLER_H #define LLVM_C_DISASSEMBLER_H -#include "llvm-c/DisassemblerTypes.h" -#include "llvm-c/ExternC.h" +#include "DisassemblerTypes.h" +#include "ExternC.h" /** * @defgroup LLVMCDisassembler Disassembler diff --git a/src/llvm-c/DisassemblerTypes.h b/src/llvm-c/DisassemblerTypes.h index 6999a350e..6b7ad6104 100644 --- a/src/llvm-c/DisassemblerTypes.h +++ b/src/llvm-c/DisassemblerTypes.h @@ -10,7 +10,7 @@ #ifndef LLVM_C_DISASSEMBLERTYPES_H #define LLVM_C_DISASSEMBLERTYPES_H -#include "llvm-c/DataTypes.h" +#include "DataTypes.h" #ifdef __cplusplus #include #else diff --git a/src/llvm-c/Error.h b/src/llvm-c/Error.h index c3baaf651..00746c701 100644 --- a/src/llvm-c/Error.h +++ b/src/llvm-c/Error.h @@ -14,7 +14,7 @@ #ifndef LLVM_C_ERROR_H #define LLVM_C_ERROR_H -#include "llvm-c/ExternC.h" +#include "ExternC.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/ErrorHandling.h b/src/llvm-c/ErrorHandling.h index d9b9f2275..7f9b50a95 100644 --- a/src/llvm-c/ErrorHandling.h +++ b/src/llvm-c/ErrorHandling.h @@ -14,7 +14,7 @@ #ifndef LLVM_C_ERRORHANDLING_H #define LLVM_C_ERRORHANDLING_H -#include "llvm-c/ExternC.h" +#include "ExternC.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/ExecutionEngine.h b/src/llvm-c/ExecutionEngine.h index c5fc9bdb4..8e72faefd 100644 --- a/src/llvm-c/ExecutionEngine.h +++ b/src/llvm-c/ExecutionEngine.h @@ -19,10 +19,10 @@ #ifndef LLVM_C_EXECUTIONENGINE_H #define LLVM_C_EXECUTIONENGINE_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Target.h" -#include "llvm-c/TargetMachine.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Target.h" +#include "TargetMachine.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/IRReader.h b/src/llvm-c/IRReader.h index 905b84fa5..ec1110c7a 100644 --- a/src/llvm-c/IRReader.h +++ b/src/llvm-c/IRReader.h @@ -14,8 +14,8 @@ #ifndef LLVM_C_IRREADER_H #define LLVM_C_IRREADER_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/LLJIT.h b/src/llvm-c/LLJIT.h index a06133aac..ee207e10e 100644 --- a/src/llvm-c/LLJIT.h +++ b/src/llvm-c/LLJIT.h @@ -1,4 +1,4 @@ -/*===----------- llvm-c/LLJIT.h - OrcV2 LLJIT C bindings --------*- C++ -*-===*\ +/*===----------- llvm-c/LLJIT.h - OrcV2 LLJIT C bindings ----------*- C -*-===*\ |* *| |* Part of the LLVM Project, under the Apache License v2.0 with LLVM *| |* Exceptions. *| @@ -24,10 +24,10 @@ #ifndef LLVM_C_LLJIT_H #define LLVM_C_LLJIT_H -#include "llvm-c/Error.h" -#include "llvm-c/Orc.h" -#include "llvm-c/TargetMachine.h" -#include "llvm-c/Types.h" +#include "Error.h" +#include "Orc.h" +#include "TargetMachine.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/LLJITUtils.h b/src/llvm-c/LLJITUtils.h new file mode 100644 index 000000000..57ffedff8 --- /dev/null +++ b/src/llvm-c/LLJITUtils.h @@ -0,0 +1,52 @@ +/*===------- llvm-c/LLJITUtils.h - Advanced LLJIT features --------*- C -*-===*\ +|* *| +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM *| +|* Exceptions. *| +|* See https://llvm.org/LICENSE.txt for license information. *| +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception *| +|* *| +|*===----------------------------------------------------------------------===*| +|* *| +|* This header declares the C interface for extra utilities to be used with *| +|* the LLJIT class from the llvm-c/LLJIT.h header. It requires to following *| +|* link libraries in addition to libLLVMOrcJIT.a: *| +|* - libLLVMOrcDebugging.a *| +|* *| +|* Many exotic languages can interoperate with C code but have a harder time *| +|* with C++ due to name mangling. So in addition to C, this interface enables *| +|* tools written in such languages. *| +|* *| +|* Note: This interface is experimental. It is *NOT* stable, and may be *| +|* changed without warning. Only C API usage documentation is *| +|* provided. See the C++ documentation for all higher level ORC API *| +|* details. *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef LLVM_C_LLJITUTILS_H +#define LLVM_C_LLJITUTILS_H + +#include "LLJIT.h" + +LLVM_C_EXTERN_C_BEGIN + +/** + * @defgroup LLVMCExecutionEngineLLJITUtils LLJIT Utilities + * @ingroup LLVMCExecutionEngineLLJIT + * + * @{ + */ + +/** + * Install the plugin that submits debug objects to the executor. Executors must + * expose the llvm_orc_registerJITLoaderGDBWrapper symbol. + */ +LLVMErrorRef LLVMOrcLLJITEnableDebugSupport(LLVMOrcLLJITRef J); + +/** + * @} + */ + +LLVM_C_EXTERN_C_END + +#endif /* LLVM_C_LLJITUTILS_H */ diff --git a/src/llvm-c/Linker.h b/src/llvm-c/Linker.h index acff5d5e2..463a2cff9 100644 --- a/src/llvm-c/Linker.h +++ b/src/llvm-c/Linker.h @@ -14,8 +14,8 @@ #ifndef LLVM_C_LINKER_H #define LLVM_C_LINKER_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/Object.h b/src/llvm-c/Object.h index f871d5230..1948c3c34 100644 --- a/src/llvm-c/Object.h +++ b/src/llvm-c/Object.h @@ -19,9 +19,9 @@ #ifndef LLVM_C_OBJECT_H #define LLVM_C_OBJECT_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" -#include "llvm-c/Config//llvm-config.h" +#include "ExternC.h" +#include "Types.h" +#include "Config/llvm-config.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/Orc.h b/src/llvm-c/Orc.h index 0dcfb0686..ecd110b4d 100644 --- a/src/llvm-c/Orc.h +++ b/src/llvm-c/Orc.h @@ -27,9 +27,9 @@ #ifndef LLVM_C_ORC_H #define LLVM_C_ORC_H -#include "llvm-c/Error.h" -#include "llvm-c/TargetMachine.h" -#include "llvm-c/Types.h" +#include "Error.h" +#include "TargetMachine.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN @@ -346,7 +346,7 @@ typedef struct LLVMOrcOpaqueLookupState *LLVMOrcLookupStateRef; * into. * * The JDLookupFlags argument can be inspected to determine whether the original - * lookup included non-exported symobls. + * lookup included non-exported symbols. * * Finally, the LookupSet argument contains the set of symbols that could not * be found in JD already (the set of generation candidates). @@ -508,7 +508,7 @@ void LLVMOrcSymbolStringPoolClearDeadEntries(LLVMOrcSymbolStringPoolRef SSP); * Intern a string in the ExecutionSession's SymbolStringPool and return a * reference to it. This increments the ref-count of the pool entry, and the * returned value should be released once the client is done with it by - * calling LLVMOrReleaseSymbolStringPoolEntry. + * calling LLVMOrcReleaseSymbolStringPoolEntry. * * Since strings are uniqued within the SymbolStringPool * LLVMOrcSymbolStringPoolEntryRefs can be compared by value to test string @@ -796,7 +796,7 @@ void LLVMOrcDisposeSymbols(LLVMOrcSymbolStringPoolEntryRef *Symbols); * method returns an error then clients should log it and call * LLVMOrcMaterializationResponsibilityFailMaterialization. If no dependencies * have been registered for the symbols covered by this - * MaterializationResponsibiility then this method is guaranteed to return + * MaterializationResponsibility then this method is guaranteed to return * LLVMErrorSuccess. */ LLVMErrorRef LLVMOrcMaterializationResponsibilityNotifyResolved( @@ -813,7 +813,7 @@ LLVMErrorRef LLVMOrcMaterializationResponsibilityNotifyResolved( * method returns an error then clients should log it and call * LLVMOrcMaterializationResponsibilityFailMaterialization. * If no dependencies have been registered for the symbols covered by this - * MaterializationResponsibiility then this method is guaranteed to return + * MaterializationResponsibility then this method is guaranteed to return * LLVMErrorSuccess. */ LLVMErrorRef LLVMOrcMaterializationResponsibilityNotifyEmitted( @@ -839,7 +839,7 @@ LLVMErrorRef LLVMOrcMaterializationResponsibilityDefineMaterializing( /** * Notify all not-yet-emitted covered by this MaterializationResponsibility * instance that an error has occurred. - * This will remove all symbols covered by this MaterializationResponsibilty + * This will remove all symbols covered by this MaterializationResponsibility * from the target JITDylib, and send an error to any queries waiting on * these symbols. */ diff --git a/src/llvm-c/OrcEE.h b/src/llvm-c/OrcEE.h index d451187aa..aef24c7aa 100644 --- a/src/llvm-c/OrcEE.h +++ b/src/llvm-c/OrcEE.h @@ -24,11 +24,11 @@ #ifndef LLVM_C_ORCEE_H #define LLVM_C_ORCEE_H -#include "llvm-c/Error.h" -#include "llvm-c/ExecutionEngine.h" -#include "llvm-c/Orc.h" -#include "llvm-c/TargetMachine.h" -#include "llvm-c/Types.h" +#include "Error.h" +#include "ExecutionEngine.h" +#include "Orc.h" +#include "TargetMachine.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/Remarks.h b/src/llvm-c/Remarks.h index ffe647a65..548a4041a 100644 --- a/src/llvm-c/Remarks.h +++ b/src/llvm-c/Remarks.h @@ -15,8 +15,8 @@ #ifndef LLVM_C_REMARKS_H #define LLVM_C_REMARKS_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" #ifdef __cplusplus #include #else diff --git a/src/llvm-c/Support.h b/src/llvm-c/Support.h index 17657861b..31a75354c 100644 --- a/src/llvm-c/Support.h +++ b/src/llvm-c/Support.h @@ -14,9 +14,9 @@ #ifndef LLVM_C_SUPPORT_H #define LLVM_C_SUPPORT_H -#include "llvm-c/DataTypes.h" -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "DataTypes.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/Target.h b/src/llvm-c/Target.h index 2bc3d1ae7..4d03741c4 100644 --- a/src/llvm-c/Target.h +++ b/src/llvm-c/Target.h @@ -19,9 +19,9 @@ #ifndef LLVM_C_TARGET_H #define LLVM_C_TARGET_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" -#include "llvm-c/Config//llvm-config.h" +#include "ExternC.h" +#include "Types.h" +#include "Config/llvm-config.h" LLVM_C_EXTERN_C_BEGIN @@ -40,34 +40,34 @@ typedef struct LLVMOpaqueTargetLibraryInfotData *LLVMTargetLibraryInfoRef; /* Declare all of the target-initialization functions that are available. */ #define LLVM_TARGET(TargetName) \ void LLVMInitialize##TargetName##TargetInfo(void); -#include "llvm-c/Config//Targets.def" +#include "Config/Targets.def" #undef LLVM_TARGET /* Explicit undef to make SWIG happier */ #define LLVM_TARGET(TargetName) void LLVMInitialize##TargetName##Target(void); -#include "llvm-c/Config//Targets.def" +#include "Config/Targets.def" #undef LLVM_TARGET /* Explicit undef to make SWIG happier */ #define LLVM_TARGET(TargetName) \ void LLVMInitialize##TargetName##TargetMC(void); -#include "llvm-c/Config//Targets.def" +#include "Config/Targets.def" #undef LLVM_TARGET /* Explicit undef to make SWIG happier */ /* Declare all of the available assembly printer initialization functions. */ #define LLVM_ASM_PRINTER(TargetName) \ void LLVMInitialize##TargetName##AsmPrinter(void); -#include "llvm-c/Config//AsmPrinters.def" +#include "Config/AsmPrinters.def" #undef LLVM_ASM_PRINTER /* Explicit undef to make SWIG happier */ /* Declare all of the available assembly parser initialization functions. */ #define LLVM_ASM_PARSER(TargetName) \ void LLVMInitialize##TargetName##AsmParser(void); -#include "llvm-c/Config//AsmParsers.def" +#include "Config/AsmParsers.def" #undef LLVM_ASM_PARSER /* Explicit undef to make SWIG happier */ /* Declare all of the available disassembler initialization functions. */ #define LLVM_DISASSEMBLER(TargetName) \ void LLVMInitialize##TargetName##Disassembler(void); -#include "llvm-c/Config//Disassemblers.def" +#include "Config/Disassemblers.def" #undef LLVM_DISASSEMBLER /* Explicit undef to make SWIG happier */ /** LLVMInitializeAllTargetInfos - The main program should call this function if @@ -75,7 +75,7 @@ typedef struct LLVMOpaqueTargetLibraryInfotData *LLVMTargetLibraryInfoRef; support. */ static inline void LLVMInitializeAllTargetInfos(void) { #define LLVM_TARGET(TargetName) LLVMInitialize##TargetName##TargetInfo(); -#include "llvm-c/Config//Targets.def" +#include "Config/Targets.def" #undef LLVM_TARGET /* Explicit undef to make SWIG happier */ } @@ -84,7 +84,7 @@ static inline void LLVMInitializeAllTargetInfos(void) { support. */ static inline void LLVMInitializeAllTargets(void) { #define LLVM_TARGET(TargetName) LLVMInitialize##TargetName##Target(); -#include "llvm-c/Config//Targets.def" +#include "Config/Targets.def" #undef LLVM_TARGET /* Explicit undef to make SWIG happier */ } @@ -93,7 +93,7 @@ static inline void LLVMInitializeAllTargets(void) { support. */ static inline void LLVMInitializeAllTargetMCs(void) { #define LLVM_TARGET(TargetName) LLVMInitialize##TargetName##TargetMC(); -#include "llvm-c/Config//Targets.def" +#include "Config/Targets.def" #undef LLVM_TARGET /* Explicit undef to make SWIG happier */ } @@ -102,7 +102,7 @@ static inline void LLVMInitializeAllTargetMCs(void) { available via the TargetRegistry. */ static inline void LLVMInitializeAllAsmPrinters(void) { #define LLVM_ASM_PRINTER(TargetName) LLVMInitialize##TargetName##AsmPrinter(); -#include "llvm-c/Config//AsmPrinters.def" +#include "Config/AsmPrinters.def" #undef LLVM_ASM_PRINTER /* Explicit undef to make SWIG happier */ } @@ -111,7 +111,7 @@ static inline void LLVMInitializeAllAsmPrinters(void) { available via the TargetRegistry. */ static inline void LLVMInitializeAllAsmParsers(void) { #define LLVM_ASM_PARSER(TargetName) LLVMInitialize##TargetName##AsmParser(); -#include "llvm-c/Config//AsmParsers.def" +#include "Config/AsmParsers.def" #undef LLVM_ASM_PARSER /* Explicit undef to make SWIG happier */ } @@ -121,7 +121,7 @@ static inline void LLVMInitializeAllAsmParsers(void) { static inline void LLVMInitializeAllDisassemblers(void) { #define LLVM_DISASSEMBLER(TargetName) \ LLVMInitialize##TargetName##Disassembler(); -#include "llvm-c/Config//Disassemblers.def" +#include "Config/Disassemblers.def" #undef LLVM_DISASSEMBLER /* Explicit undef to make SWIG happier */ } diff --git a/src/llvm-c/TargetMachine.h b/src/llvm-c/TargetMachine.h index bfbe1421a..aa628e216 100644 --- a/src/llvm-c/TargetMachine.h +++ b/src/llvm-c/TargetMachine.h @@ -19,9 +19,9 @@ #ifndef LLVM_C_TARGETMACHINE_H #define LLVM_C_TARGETMACHINE_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Target.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Target.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN @@ -31,6 +31,7 @@ LLVM_C_EXTERN_C_BEGIN * @{ */ +typedef struct LLVMOpaqueTargetMachineOptions *LLVMTargetMachineOptionsRef; typedef struct LLVMOpaqueTargetMachine *LLVMTargetMachineRef; typedef struct LLVMTarget *LLVMTargetRef; @@ -66,6 +67,12 @@ typedef enum { LLVMObjectFile } LLVMCodeGenFileType; +typedef enum { + LLVMGlobalISelAbortEnable, + LLVMGlobalISelAbortDisable, + LLVMGlobalISelAbortDisableWithDiag, +} LLVMGlobalISelAbortMode; + /** Returns the first llvm::Target in the registered targets list. */ LLVMTargetRef LLVMGetFirstTarget(void); /** Returns the next llvm::Target given a previous one (or null if there's none) */ @@ -98,6 +105,55 @@ LLVMBool LLVMTargetHasTargetMachine(LLVMTargetRef T); LLVMBool LLVMTargetHasAsmBackend(LLVMTargetRef T); /*===-- Target Machine ----------------------------------------------------===*/ +/** + * Create a new set of options for an llvm::TargetMachine. + * + * The returned option structure must be released with + * LLVMDisposeTargetMachineOptions() after the call to + * LLVMCreateTargetMachineWithOptions(). + */ +LLVMTargetMachineOptionsRef LLVMCreateTargetMachineOptions(void); + +/** + * Dispose of an LLVMTargetMachineOptionsRef instance. + */ +void LLVMDisposeTargetMachineOptions(LLVMTargetMachineOptionsRef Options); + +void LLVMTargetMachineOptionsSetCPU(LLVMTargetMachineOptionsRef Options, + const char *CPU); + +/** + * Set the list of features for the target machine. + * + * \param Features a comma-separated list of features. + */ +void LLVMTargetMachineOptionsSetFeatures(LLVMTargetMachineOptionsRef Options, + const char *Features); + +void LLVMTargetMachineOptionsSetABI(LLVMTargetMachineOptionsRef Options, + const char *ABI); + +void LLVMTargetMachineOptionsSetCodeGenOptLevel( + LLVMTargetMachineOptionsRef Options, LLVMCodeGenOptLevel Level); + +void LLVMTargetMachineOptionsSetRelocMode(LLVMTargetMachineOptionsRef Options, + LLVMRelocMode Reloc); + +void LLVMTargetMachineOptionsSetCodeModel(LLVMTargetMachineOptionsRef Options, + LLVMCodeModel CodeModel); + +/** + * Create a new llvm::TargetMachine. + * + * \param T the target to create a machine for. + * \param Triple a triple describing the target machine. + * \param Options additional configuration (see + * LLVMCreateTargetMachineOptions()). + */ +LLVMTargetMachineRef +LLVMCreateTargetMachineWithOptions(LLVMTargetRef T, const char *Triple, + LLVMTargetMachineOptionsRef Options); + /** Creates a new llvm::TargetMachine. See llvm::Target::createTargetMachine */ LLVMTargetMachineRef LLVMCreateTargetMachine(LLVMTargetRef T, const char *Triple, const char *CPU, const char *Features, @@ -132,6 +188,21 @@ LLVMTargetDataRef LLVMCreateTargetDataLayout(LLVMTargetMachineRef T); void LLVMSetTargetMachineAsmVerbosity(LLVMTargetMachineRef T, LLVMBool VerboseAsm); +/** Enable fast-path instruction selection. */ +void LLVMSetTargetMachineFastISel(LLVMTargetMachineRef T, LLVMBool Enable); + +/** Enable global instruction selection. */ +void LLVMSetTargetMachineGlobalISel(LLVMTargetMachineRef T, LLVMBool Enable); + +/** Set abort behaviour when global instruction selection fails to lower/select + * an instruction. */ +void LLVMSetTargetMachineGlobalISelAbort(LLVMTargetMachineRef T, + LLVMGlobalISelAbortMode Mode); + +/** Enable the MachineOutliner pass. */ +void LLVMSetTargetMachineMachineOutliner(LLVMTargetMachineRef T, + LLVMBool Enable); + /** Emits an asm or object file for the given module to the filename. This wraps several c++ only classes (among them a file stream). Returns any error in ErrorMessage. Use LLVMDisposeMessage to dispose the message. */ diff --git a/src/llvm-c/Transforms/PassBuilder.h b/src/llvm-c/Transforms/PassBuilder.h index d0466dd7f..8ad2a9982 100644 --- a/src/llvm-c/Transforms/PassBuilder.h +++ b/src/llvm-c/Transforms/PassBuilder.h @@ -14,9 +14,9 @@ #ifndef LLVM_C_TRANSFORMS_PASSBUILDER_H #define LLVM_C_TRANSFORMS_PASSBUILDER_H -#include "llvm-c/Error.h" -#include "llvm-c/TargetMachine.h" -#include "llvm-c/Types.h" +#include "../Error.h" +#include "../TargetMachine.h" +#include "../Types.h" /** * @defgroup LLVMCCoreNewPM New Pass Manager diff --git a/src/llvm-c/Types.h b/src/llvm-c/Types.h index 4e9967372..77aa7c9b4 100644 --- a/src/llvm-c/Types.h +++ b/src/llvm-c/Types.h @@ -14,8 +14,8 @@ #ifndef LLVM_C_TYPES_H #define LLVM_C_TYPES_H -#include "llvm-c/DataTypes.h" -#include "llvm-c/ExternC.h" +#include "DataTypes.h" +#include "ExternC.h" LLVM_C_EXTERN_C_BEGIN @@ -132,6 +132,11 @@ typedef struct LLVMOpaquePassManager *LLVMPassManagerRef; * @see llvm::Use */ typedef struct LLVMOpaqueUse *LLVMUseRef; +/** + * @see llvm::OperandBundleDef + */ +typedef struct LLVMOpaqueOperandBundle *LLVMOperandBundleRef; + /** * Used to represent an attributes. * diff --git a/src/llvm-c/lto.h b/src/llvm-c/lto.h index 5ceb02224..89f76c695 100644 --- a/src/llvm-c/lto.h +++ b/src/llvm-c/lto.h @@ -16,7 +16,7 @@ #ifndef LLVM_C_LTO_H #define LLVM_C_LTO_H -#include "llvm-c/ExternC.h" +#include "ExternC.h" #ifdef __cplusplus #include diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 88bb58c55..c21cd0a46 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -15,6 +15,7 @@ struct lbArgType { LLVMAttributeRef align_attribute; // Optional i64 byval_alignment; bool is_byval; + bool no_capture; }; @@ -159,6 +160,11 @@ gb_internal void lb_add_function_type_attributes(LLVMValueRef fn, lbFunctionType LLVMAddAttributeAtIndex(fn, arg_index+1, arg->align_attribute); } + if (arg->no_capture) { + LLVMAddAttributeAtIndex(fn, arg_index+1, nocapture_attr); + } + + if (ft->multiple_return_original_type) { if (ft->original_arg_count <= i) { LLVMAddAttributeAtIndex(fn, arg_index+1, noalias_attr); @@ -645,10 +651,10 @@ namespace lbAbiAmd64SysV { if (is_mem_cls(cls, attribute_kind)) { LLVMAttributeRef attribute = nullptr; if (attribute_kind == Amd64TypeAttribute_ByVal) { - // if (!is_calling_convention_odin(calling_convention)) { - return lb_arg_type_indirect_byval(c, type); - // } - // attribute = nullptr; + if (is_calling_convention_odin(calling_convention)) { + return lb_arg_type_indirect(type, attribute); + } + return lb_arg_type_indirect_byval(c, type); } else if (attribute_kind == Amd64TypeAttribute_StructRect) { attribute = lb_create_enum_attribute_with_type(c, "sret", type); } @@ -900,7 +906,15 @@ namespace lbAbiAmd64SysV { } switch (LLVMGetTypeKind(t)) { - case LLVMIntegerTypeKind: + case LLVMIntegerTypeKind: { + i64 s = t_size; + while (s > 0) { + unify(cls, ix + off/8, RegClass_Int); + off += 8; + s -= 8; + } + break; + } case LLVMPointerTypeKind: case LLVMHalfTypeKind: unify(cls, ix + off/8, RegClass_Int); @@ -1131,8 +1145,9 @@ namespace lbAbiArm64 { if (size <= 16) { LLVMTypeRef cast_type = nullptr; - GB_ASSERT(size > 0); - if (size <= 8) { + if (size == 0) { + cast_type = LLVMStructTypeInContext(c, nullptr, 0, false); + } else if (size <= 8) { cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8)); } else { unsigned count = cast(unsigned)((size+7)/8); @@ -1230,9 +1245,9 @@ namespace lbAbiWasm { gb_internal LB_ABI_INFO(abi_info) { lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; + ft->calling_convention = calling_convention; ft->args = compute_arg_types(c, arg_types, arg_count, calling_convention, original_type); ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); - ft->calling_convention = calling_convention; return ft; } @@ -1369,14 +1384,14 @@ namespace lbAbiWasm { } else if (lb_is_type_kind(return_type, LLVMStructTypeKind) || lb_is_type_kind(return_type, LLVMArrayTypeKind)) { if (type_can_be_direct(return_type, ft->calling_convention)) { return lb_arg_type_direct(return_type); - } - - i64 sz = lb_sizeof(return_type); - switch (sz) { - case 1: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 8), nullptr, nullptr); - case 2: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 16), nullptr, nullptr); - case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); - case 8: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 64), nullptr, nullptr); + } else if (ft->calling_convention != ProcCC_CDecl) { + i64 sz = lb_sizeof(return_type); + switch (sz) { + case 1: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 8), nullptr, nullptr); + case 2: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 16), nullptr, nullptr); + case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); + case 8: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 64), nullptr, nullptr); + } } LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO(); diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 4b94cf020..62909dafb 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1,11 +1,14 @@ #define MULTITHREAD_OBJECT_GENERATION 1 +#ifndef MULTITHREAD_OBJECT_GENERATION +#define MULTITHREAD_OBJECT_GENERATION 0 +#endif #ifndef USE_SEPARATE_MODULES #define USE_SEPARATE_MODULES build_context.use_separate_modules #endif -#ifndef MULTITHREAD_OBJECT_GENERATION -#define MULTITHREAD_OBJECT_GENERATION 0 +#ifndef LLVM_IGNORE_VERIFICATION +#define LLVM_IGNORE_VERIFICATION 0 #endif @@ -41,6 +44,37 @@ String get_default_microarchitecture() { return default_march; } +String get_final_microarchitecture() { + BuildContext *bc = &build_context; + + String microarch = bc->microarch; + if (microarch.len == 0) { + microarch = get_default_microarchitecture(); + } else if (microarch == str_lit("native")) { + microarch = make_string_c(LLVMGetHostCPUName()); + } + return microarch; +} + +gb_internal String get_default_features() { + BuildContext *bc = &build_context; + + int off = 0; + for (int i = 0; i < bc->metrics.arch; i += 1) { + off += target_microarch_counts[i]; + } + + String microarch = get_final_microarchitecture(); + 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"); + return {}; +} + gb_internal void lb_add_foreign_library_path(lbModule *m, Entity *e) { if (e == nullptr) { return; @@ -101,19 +135,28 @@ gb_internal void lb_set_entity_from_other_modules_linkage_correctly(lbModule *ot if (other_module == nullptr) { return; } - char const *cname = alloc_cstring(temporary_allocator(), name); + char const *cname = alloc_cstring(permanent_allocator(), name); + mpsc_enqueue(&other_module->gen->entities_to_correct_linkage, lbEntityCorrection{other_module, e, cname}); +} - LLVMValueRef other_global = nullptr; - if (e->kind == Entity_Variable) { - other_global = LLVMGetNamedGlobal(other_module->mod, cname); - } else if (e->kind == Entity_Procedure) { - other_global = LLVMGetNamedFunction(other_module->mod, cname); - } - if (other_global) { - LLVMSetLinkage(other_global, LLVMExternalLinkage); +gb_internal void lb_correct_entity_linkage(lbGenerator *gen) { + for (lbEntityCorrection ec = {}; mpsc_dequeue(&gen->entities_to_correct_linkage, &ec); /**/) { + LLVMValueRef other_global = nullptr; + if (ec.e->kind == Entity_Variable) { + other_global = LLVMGetNamedGlobal(ec.other_module->mod, ec.cname); + if (other_global) { + LLVMSetLinkage(other_global, LLVMWeakAnyLinkage); + } + } else if (ec.e->kind == Entity_Procedure) { + other_global = LLVMGetNamedFunction(ec.other_module->mod, ec.cname); + if (other_global) { + LLVMSetLinkage(other_global, LLVMWeakAnyLinkage); + } + } } } + gb_internal void lb_emit_init_context(lbProcedure *p, lbAddr addr) { TEMPORARY_ALLOCATOR_GUARD(); @@ -1094,6 +1137,53 @@ gb_internal void lb_finalize_objc_names(lbProcedure *p) { lb_end_procedure_body(p); } +gb_internal void lb_verify_function(lbModule *m, lbProcedure *p, bool dump_ll=false) { + if (LLVM_IGNORE_VERIFICATION) { + return; + } + + if (!m->debug_builder && LLVMVerifyFunction(p->value, LLVMReturnStatusAction)) { + char *llvm_error = nullptr; + + gb_printf_err("LLVM CODE GEN FAILED FOR PROCEDURE: %.*s\n", LIT(p->name)); + LLVMDumpValue(p->value); + gb_printf_err("\n"); + if (dump_ll) { + gb_printf_err("\n\n\n"); + String filepath_ll = lb_filepath_ll_for_module(m); + if (LLVMPrintModuleToFile(m->mod, cast(char const *)filepath_ll.text, &llvm_error)) { + gb_printf_err("LLVM Error: %s\n", llvm_error); + } + } + LLVMVerifyFunction(p->value, LLVMPrintMessageAction); + exit_with_errors(); + } +} + +gb_internal WORKER_TASK_PROC(lb_llvm_module_verification_worker_proc) { + char *llvm_error = nullptr; + defer (LLVMDisposeMessage(llvm_error)); + lbModule *m = cast(lbModule *)data; + + if (LLVMVerifyModule(m->mod, LLVMReturnStatusAction, &llvm_error)) { + gb_printf_err("LLVM Error:\n%s\n", llvm_error); + if (build_context.keep_temp_files) { + TIME_SECTION("LLVM Print Module to File"); + String filepath_ll = lb_filepath_ll_for_module(m); + if (LLVMPrintModuleToFile(m->mod, cast(char const *)filepath_ll.text, &llvm_error)) { + gb_printf_err("LLVM Error: %s\n", llvm_error); + exit_with_errors(); + return false; + } + } + exit_with_errors(); + return 1; + } + return 0; +} + + + gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProcedure *objc_names, Array &global_variables) { // Startup Runtime Type *proc_type = alloc_type_proc(nullptr, nullptr, 0, nullptr, 0, false, ProcCC_Odin); @@ -1129,6 +1219,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc if (is_type_untyped_nil(init.type)) { LLVMSetInitializer(var.var.value, LLVMConstNull(global_type)); var.is_initialized = true; + + if (e->Variable.is_rodata) { + LLVMSetGlobalConstant(var.var.value, true); + } continue; } GB_PANIC("Invalid init value, got %s", expr_to_string(init_expr)); @@ -1143,6 +1237,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc } LLVMSetInitializer(var.var.value, init.value); var.is_initialized = true; + + if (e->Variable.is_rodata) { + LLVMSetGlobalConstant(var.var.value, true); + } continue; } } else { @@ -1175,8 +1273,9 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc var.is_initialized = true; } + + } - CheckerInfo *info = main_module->gen->info; for (Entity *e : info->init_procedures) { @@ -1187,13 +1286,7 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc lb_end_procedure_body(p); - if (!main_module->debug_builder && LLVMVerifyFunction(p->value, LLVMReturnStatusAction)) { - gb_printf_err("LLVM CODE GEN FAILED FOR PROCEDURE: %s\n", "main"); - LLVMDumpValue(p->value); - gb_printf_err("\n\n\n\n"); - LLVMVerifyFunction(p->value, LLVMAbortProcessAction); - } - + lb_verify_function(main_module, p); return p; } @@ -1216,35 +1309,43 @@ gb_internal lbProcedure *lb_create_cleanup_runtime(lbModule *main_module) { // C lb_end_procedure_body(p); - if (!main_module->debug_builder && LLVMVerifyFunction(p->value, LLVMReturnStatusAction)) { - gb_printf_err("LLVM CODE GEN FAILED FOR PROCEDURE: %s\n", "main"); - LLVMDumpValue(p->value); - gb_printf_err("\n\n\n\n"); - LLVMVerifyFunction(p->value, LLVMAbortProcessAction); - } - + lb_verify_function(main_module, p); return p; } gb_internal WORKER_TASK_PROC(lb_generate_procedures_and_types_per_module) { lbModule *m = cast(lbModule *)data; - for (Entity *e : m->global_procedures_and_types_to_create) { - if (e->kind == Entity_TypeName) { - (void)lb_get_entity_name(m, e); - lb_type(m, e->type); - } + for (Entity *e : m->global_types_to_create) { + (void)lb_get_entity_name(m, e); + (void)lb_type(m, e->type); } - for (Entity *e : m->global_procedures_and_types_to_create) { - if (e->kind == Entity_Procedure) { - (void)lb_get_entity_name(m, e); - array_add(&m->procedures_to_generate, lb_create_procedure(m, e)); - } + for (Entity *e : m->global_procedures_to_create) { + (void)lb_get_entity_name(m, e); + array_add(&m->procedures_to_generate, lb_create_procedure(m, e)); } return 0; } +gb_internal GB_COMPARE_PROC(llvm_global_entity_cmp) { + Entity *x = *cast(Entity **)a; + Entity *y = *cast(Entity **)b; + if (x == y) { + return 0; + } + if (x->kind != y->kind) { + return cast(i32)(x->kind - y->kind); + } + + i32 cmp = 0; + cmp = token_pos_cmp(x->token.pos, y->token.pos); + if (!cmp) { + return cmp; + } + return cmp; +} + gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, CheckerInfo *info, bool do_threading) { auto *min_dep_set = &info->minimum_dependency_set; @@ -1293,17 +1394,32 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker if (USE_SEPARATE_MODULES) { m = lb_module_of_entity(gen, e); } + GB_ASSERT(m != nullptr); - array_add(&m->global_procedures_and_types_to_create, e); + if (e->kind == Entity_Procedure) { + array_add(&m->global_procedures_to_create, e); + } else if (e->kind == Entity_TypeName) { + array_add(&m->global_types_to_create, e); + } } for (auto const &entry : gen->modules) { lbModule *m = entry.value; - if (do_threading) { + array_sort(m->global_types_to_create, llvm_global_entity_cmp); + array_sort(m->global_procedures_to_create, llvm_global_entity_cmp); + } + + if (do_threading) { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; thread_pool_add_task(lb_generate_procedures_and_types_per_module, m); - } else { + } + } else { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; lb_generate_procedures_and_types_per_module(m); } + } thread_pool_wait(); @@ -1324,7 +1440,9 @@ gb_internal bool lb_is_module_empty(lbModule *m) { } for (auto g = LLVMGetFirstGlobal(m->mod); g != nullptr; g = LLVMGetNextGlobal(g)) { - if (LLVMGetLinkage(g) == LLVMExternalLinkage) { + LLVMLinkage linkage = LLVMGetLinkage(g); + if (linkage == LLVMExternalLinkage || + linkage == LLVMWeakAnyLinkage) { continue; } if (!LLVMIsExternallyInitialized(g)) { @@ -1378,10 +1496,6 @@ gb_internal WORKER_TASK_PROC(lb_llvm_function_pass_per_module) { lb_populate_function_pass_manager(m, m->function_pass_managers[lbFunctionPassManager_default], false, build_context.optimization_level); lb_populate_function_pass_manager(m, m->function_pass_managers[lbFunctionPassManager_default_without_memcpy], true, build_context.optimization_level); lb_populate_function_pass_manager_specific(m, m->function_pass_managers[lbFunctionPassManager_none], -1); - lb_populate_function_pass_manager_specific(m, m->function_pass_managers[lbFunctionPassManager_minimal], 0); - lb_populate_function_pass_manager_specific(m, m->function_pass_managers[lbFunctionPassManager_size], 1); - lb_populate_function_pass_manager_specific(m, m->function_pass_managers[lbFunctionPassManager_speed], 2); - lb_populate_function_pass_manager_specific(m, m->function_pass_managers[lbFunctionPassManager_aggressive], 3); for (i32 i = 0; i < lbFunctionPassManager_COUNT; i++) { LLVMFinalizeFunctionPassManager(m->function_pass_managers[i]); @@ -1405,15 +1519,12 @@ gb_internal WORKER_TASK_PROC(lb_llvm_function_pass_per_module) { if (p->entity && p->entity->kind == Entity_Procedure) { switch (p->entity->Procedure.optimization_mode) { case ProcedureOptimizationMode_None: - case ProcedureOptimizationMode_Minimal: - pass_manager_kind = lbFunctionPassManager_minimal; + pass_manager_kind = lbFunctionPassManager_none; + GB_ASSERT(lb_proc_has_attribute(p->module, p->value, "optnone")); + GB_ASSERT(lb_proc_has_attribute(p->module, p->value, "noinline")); break; - case ProcedureOptimizationMode_Size: - pass_manager_kind = lbFunctionPassManager_size; - lb_add_attribute_to_proc(p->module, p->value, "optsize"); - break; - case ProcedureOptimizationMode_Speed: - pass_manager_kind = lbFunctionPassManager_speed; + case ProcedureOptimizationMode_FavorSize: + GB_ASSERT(lb_proc_has_attribute(p->module, p->value, "optsize")); break; } } @@ -1469,6 +1580,7 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_pass_worker_proc) { switch (build_context.optimization_level) { case -1: + array_add(&passes, "function(annotation-remarks)"); break; case 0: array_add(&passes, "always-inline"); @@ -1477,6 +1589,7 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_pass_worker_proc) { case 1: // default // Passes removed: coro, openmp, sroa +#if LLVM_VERSION_MAJOR == 17 array_add(&passes, u8R"( annotation2metadata, forceattrs, @@ -1492,13 +1605,14 @@ globalopt, function( mem2reg, instcombine, - simplifycfg), - require, - function( - invalidate - ), - require, - cgscc( + simplifycfg +), +require, +function( + invalidate +), +require, +cgscc( devirt<4>( inline, inline, @@ -1599,10 +1713,142 @@ function( ), verify )"); +#else + array_add(&passes, u8R"( +annotation2metadata, +forceattrs, +inferattrs, +function( + lower-expect, + simplifycfg, + sroa, + early-cse<> +), +ipsccp, +called-value-propagation, +globalopt, +function( + mem2reg, + instcombine, + simplifycfg +), +always-inline, +require, +function( + invalidate +), +require, +cgscc( + devirt<4>( + inline, + function-attrs, + function( + sroa, + early-cse, + speculative-execution, + jump-threading, + correlated-propagation, + simplifycfg, + instcombine, + aggressive-instcombine, + tailcallelim, + simplifycfg, + reassociate, + constraint-elimination, + loop-mssa( + loop-instsimplify, + loop-simplifycfg, + licm, + loop-rotate, + licm, + simple-loop-unswitch + ), + simplifycfg, + instcombine, + loop( + loop-idiom, + indvars, + loop-deletion, + loop-unroll-full + ), + sroa, + vector-combine, + mldst-motion, + gvn<>, + sccp, + bdce, + instcombine, + jump-threading, + correlated-propagation, + adce, + memcpyopt, + dse, + move-auto-init, + loop-mssa( + licm + ), + simplifycfg, + instcombine + ), + function-attrs, + function( + require + ) + ) +), +deadargelim, +globalopt, +globaldce, +elim-avail-extern, +rpo-function-attrs, +recompute-globalsaa, +function( + float2int, + lower-constant-intrinsics, + loop( + loop-rotate, + loop-deletion + ), + loop-distribute, + inject-tli-mappings, + loop-vectorize, + infer-alignment, + loop-load-elim, + instcombine, + simplifycfg, + slp-vectorizer, + vector-combine, + instcombine, + loop-unroll, + transform-warning, + sroa, + infer-alignment, + instcombine, + loop-mssa( + licm + ), + alignment-from-assumptions, + loop-sink, + instsimplify, + div-rem-pairs, + tailcallelim, + simplifycfg +), +globaldce, +constmerge, +cg-profile, +rel-lookup-table-converter, +function( + annotation-remarks +), +verify +)"); +#endif break; // default // Passes removed: coro, openmp, sroa case 2: +#if LLVM_VERSION_MAJOR == 17 array_add(&passes, u8R"( annotation2metadata, forceattrs, @@ -1727,11 +1973,144 @@ function( ), verify )"); +#else + array_add(&passes, u8R"( +annotation2metadata, +forceattrs, +inferattrs, +function( + lower-expect, + simplifycfg, + sroa, + early-cse<> +), +ipsccp, +called-value-propagation, +globalopt, +function( + mem2reg, + instcombine, + simplifycfg +), +always-inline, +require, +function( + invalidate +), +require, +cgscc( + devirt<4>( + inline, + function-attrs, + function( + sroa, + early-cse, + speculative-execution, + jump-threading, + correlated-propagation, + simplifycfg, + instcombine, + aggressive-instcombine, + libcalls-shrinkwrap, + tailcallelim, + simplifycfg, + reassociate, + constraint-elimination, + loop-mssa( + loop-instsimplify, + loop-simplifycfg, + licm, + loop-rotate, + licm, + simple-loop-unswitch + ), + simplifycfg, + instcombine, + loop( + loop-idiom, + indvars, + loop-deletion, + loop-unroll-full + ), + sroa, + vector-combine, + mldst-motion, + gvn<>, + sccp, + bdce, + instcombine, + jump-threading, + correlated-propagation, + adce, + memcpyopt, + dse, + move-auto-init, + loop-mssa( + licm + ), + simplifycfg, + instcombine + ), + function-attrs, + function( + require + ) + ) +), +deadargelim, +globalopt, +globaldce, +elim-avail-extern, +rpo-function-attrs, +recompute-globalsaa, +function( + float2int, + lower-constant-intrinsics, + loop( + loop-rotate, + loop-deletion + ), + loop-distribute, + inject-tli-mappings, + loop-vectorize, + infer-alignment, + loop-load-elim, + instcombine, + simplifycfg, + slp-vectorizer, + vector-combine, + instcombine, + loop-unroll, + transform-warning, + sroa, + infer-alignment, + instcombine, + loop-mssa( + licm + ), + alignment-from-assumptions, + loop-sink, + instsimplify, + div-rem-pairs, + tailcallelim, + simplifycfg +), +globaldce, +constmerge, +cg-profile, +rel-lookup-table-converter, +function( + annotation-remarks +), +verify +)"); +#endif break; case 3: // default // Passes removed: coro, openmp, sroa +#if LLVM_VERSION_MAJOR == 17 array_add(&passes, u8R"( annotation2metadata, forceattrs, @@ -1859,6 +2238,135 @@ function( ), verify )"); +#else + array_add(&passes, u8R"( +annotation2metadata, +forceattrs, +inferattrs, +function( + lower-expect, + simplifycfg, + sroa, + early-cse<>, + callsite-splitting +), +ipsccp, +called-value-propagation, +globalopt, +function( + mem2reg, + instcombine, + simplifycfg +), +always-inline, +require, +function(invalidate), +require, +cgscc( + devirt<4>( + inline, + function-attrs, + argpromotion, + function( + sroa, + early-cse, + speculative-execution, + jump-threading, + correlated-propagation, + simplifycfg, + instcombine, + aggressive-instcombine, + libcalls-shrinkwrap, + tailcallelim, + simplifycfg, + reassociate, + constraint-elimination, + loop-mssa( + loop-instsimplify, + loop-simplifycfg, + licm, + loop-rotate, + licm, + simple-loop-unswitch + ), + simplifycfg, + instcombine, + loop( + loop-idiom, + indvars, + loop-deletion, + loop-unroll-full + ), + sroa, + vector-combine, + mldst-motion, + gvn<>, + sccp, + bdce, + instcombine, + jump-threading, + correlated-propagation, + adce, + memcpyopt, + dse, + move-auto-init, + loop-mssa(licm), + simplifycfg, + instcombine + ), + function-attrs, + function( + require + ) + ) +), +deadargelim, +globalopt, +globaldce, +elim-avail-extern, +rpo-function-attrs, +recompute-globalsaa, +function( + float2int, + lower-constant-intrinsics, + chr, + loop( + loop-rotate, + loop-deletion + ), + loop-distribute, + inject-tli-mappings, + loop-vectorize, + infer-alignment, + loop-load-elim, + instcombine, + simplifycfg, + slp-vectorizer, + vector-combine, + instcombine, + loop-unroll, + transform-warning, + sroa, + infer-alignment, + instcombine, + loop-mssa(licm), + alignment-from-assumptions, + loop-sink, + instsimplify, + div-rem-pairs, + tailcallelim, + simplifycfg +), +globaldce, +constmerge, +cg-profile, +rel-lookup-table-converter, +function( + annotation-remarks +), +verify +)"); +#endif break; } @@ -1938,16 +2446,19 @@ gb_internal WORKER_TASK_PROC(lb_generate_procedures_worker_proc) { } gb_internal void lb_generate_procedures(lbGenerator *gen, bool do_threading) { - for (auto const &entry : gen->modules) { - lbModule *m = entry.value; - if (do_threading) { + if (do_threading) { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; thread_pool_add_task(lb_generate_procedures_worker_proc, m); - } else { + } + + thread_pool_wait(); + } else { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; lb_generate_procedures_worker_proc(m); } } - - thread_pool_wait(); } gb_internal WORKER_TASK_PROC(lb_generate_missing_procedures_to_check_worker_proc) { @@ -1961,17 +2472,20 @@ gb_internal WORKER_TASK_PROC(lb_generate_missing_procedures_to_check_worker_proc } gb_internal void lb_generate_missing_procedures(lbGenerator *gen, bool do_threading) { - for (auto const &entry : gen->modules) { - lbModule *m = entry.value; - // NOTE(bill): procedures may be added during generation - if (do_threading) { + if (do_threading) { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; + // NOTE(bill): procedures may be added during generation thread_pool_add_task(lb_generate_missing_procedures_to_check_worker_proc, m); - } else { + } + thread_pool_wait(); + } else { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; + // NOTE(bill): procedures may be added during generation lb_generate_missing_procedures_to_check_worker_proc(m); } } - - thread_pool_wait(); } gb_internal void lb_debug_info_complete_types_and_finalize(lbGenerator *gen) { @@ -1984,32 +2498,45 @@ gb_internal void lb_debug_info_complete_types_and_finalize(lbGenerator *gen) { } gb_internal void lb_llvm_function_passes(lbGenerator *gen, bool do_threading) { - for (auto const &entry : gen->modules) { - lbModule *m = entry.value; - if (do_threading) { + if (do_threading) { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; thread_pool_add_task(lb_llvm_function_pass_per_module, m); - } else { + } + thread_pool_wait(); + } else { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; lb_llvm_function_pass_per_module(m); } } - thread_pool_wait(); } gb_internal void lb_llvm_module_passes(lbGenerator *gen, bool do_threading) { - for (auto const &entry : gen->modules) { - lbModule *m = entry.value; - auto wd = gb_alloc_item(permanent_allocator(), lbLLVMModulePassWorkerData); - wd->m = m; - wd->target_machine = m->target_machine; + if (do_threading) { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; + auto wd = gb_alloc_item(permanent_allocator(), lbLLVMModulePassWorkerData); + wd->m = m; + wd->target_machine = m->target_machine; - if (do_threading) { - thread_pool_add_task(lb_llvm_module_pass_worker_proc, wd); - } else { + if (do_threading) { + thread_pool_add_task(lb_llvm_module_pass_worker_proc, wd); + } else { + lb_llvm_module_pass_worker_proc(wd); + } + } + thread_pool_wait(); + } else { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; + auto wd = gb_alloc_item(permanent_allocator(), lbLLVMModulePassWorkerData); + wd->m = m; + wd->target_machine = m->target_machine; lb_llvm_module_pass_worker_proc(wd); } } - thread_pool_wait(); } gb_internal String lb_filepath_ll_for_module(lbModule *m) { @@ -2033,20 +2560,37 @@ gb_internal String lb_filepath_ll_for_module(lbModule *m) { return path; } + gb_internal String lb_filepath_obj_for_module(lbModule *m) { - String path = concatenate3_strings(permanent_allocator(), - build_context.build_paths[BuildPath_Output].basename, - STR_LIT("/"), - build_context.build_paths[BuildPath_Output].name - ); + String basename = build_context.build_paths[BuildPath_Output].basename; + String name = build_context.build_paths[BuildPath_Output].name; + + bool use_temporary_directory = false; + if (USE_SEPARATE_MODULES && build_context.build_mode == BuildMode_Executable) { + // NOTE(bill): use a temporary directory + String dir = temporary_directory(permanent_allocator()); + if (dir.len != 0) { + basename = dir; + use_temporary_directory = true; + } + } + + gbString path = gb_string_make_length(heap_allocator(), basename.text, basename.len); + path = gb_string_appendc(path, "/"); + path = gb_string_append_length(path, name.text, name.len); if (m->file) { char buf[32] = {}; isize n = gb_snprintf(buf, gb_size_of(buf), "-%u", m->file->id); String suffix = make_string((u8 *)buf, n-1); - path = concatenate_strings(permanent_allocator(), path, suffix); + path = gb_string_append_length(path, suffix.text, suffix.len); } else if (m->pkg) { - path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name); + path = gb_string_appendc(path, "-"); + path = gb_string_append_length(path, m->pkg->name.text, m->pkg->name.len); + } + + if (use_temporary_directory) { + path = gb_string_append_fmt(path, "-%p", m); } String ext = {}; @@ -2084,43 +2628,33 @@ gb_internal String lb_filepath_obj_for_module(lbModule *m) { } } - return concatenate_strings(permanent_allocator(), path, ext); -} + path = gb_string_append_length(path, ext.text, ext.len); + + return make_string(cast(u8 *)path, gb_string_length(path)); -gb_internal WORKER_TASK_PROC(lb_llvm_module_verification_worker_proc) { - char *llvm_error = nullptr; - defer (LLVMDisposeMessage(llvm_error)); - lbModule *m = cast(lbModule *)data; - if (LLVMVerifyModule(m->mod, LLVMReturnStatusAction, &llvm_error)) { - gb_printf_err("LLVM Error:\n%s\n", llvm_error); - if (build_context.keep_temp_files) { - TIME_SECTION("LLVM Print Module to File"); - String filepath_ll = lb_filepath_ll_for_module(m); - if (LLVMPrintModuleToFile(m->mod, cast(char const *)filepath_ll.text, &llvm_error)) { - gb_printf_err("LLVM Error: %s\n", llvm_error); - exit_with_errors(); - return false; - } - } - exit_with_errors(); - return 1; - } - return 0; } gb_internal bool lb_llvm_module_verification(lbGenerator *gen, bool do_threading) { - for (auto const &entry : gen->modules) { - lbModule *m = entry.value; - if (do_threading) { + if (LLVM_IGNORE_VERIFICATION) { + return true; + } + + if (do_threading) { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; thread_pool_add_task(lb_llvm_module_verification_worker_proc, m); - } else { + } + thread_pool_wait(); + + } else { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; if (lb_llvm_module_verification_worker_proc(m)) { return false; } } } - thread_pool_wait(); return true; } @@ -2156,7 +2690,6 @@ gb_internal bool lb_llvm_object_generation(lbGenerator *gen, bool do_threading) String filepath_ll = lb_filepath_ll_for_module(m); String filepath_obj = lb_filepath_obj_for_module(m); - // gb_printf_err("%.*s\n", LIT(filepath_obj)); array_add(&gen->output_object_paths, filepath_obj); array_add(&gen->output_temp_paths, filepath_ll); @@ -2341,12 +2874,7 @@ gb_internal lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *star } - if (!m->debug_builder && LLVMVerifyFunction(p->value, LLVMReturnStatusAction)) { - gb_printf_err("LLVM CODE GEN FAILED FOR PROCEDURE: %s\n", "main"); - LLVMDumpValue(p->value); - gb_printf_err("\n\n\n\n"); - LLVMVerifyFunction(p->value, LLVMAbortProcessAction); - } + lb_verify_function(m, p); lb_run_function_pass_manager(default_function_pass_manager, p, lbFunctionPassManager_default); return p; @@ -2367,28 +2895,11 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { lb_end_procedure(p); // Add Flags - if (p->body != nullptr) { - if (p->name == "memcpy" || p->name == "memmove" || - p->name == "runtime.mem_copy" || p->name == "mem_copy_non_overlapping" || - string_starts_with(p->name, str_lit("llvm.memcpy")) || - string_starts_with(p->name, str_lit("llvm.memmove"))) { - p->flags |= lbProcedureFlag_WithoutMemcpyPass; - } + if (p->entity && p->entity->kind == Entity_Procedure && p->entity->Procedure.is_memcpy_like) { + p->flags |= lbProcedureFlag_WithoutMemcpyPass; } - if (!m->debug_builder && LLVMVerifyFunction(p->value, LLVMReturnStatusAction)) { - char *llvm_error = nullptr; - - gb_printf_err("LLVM CODE GEN FAILED FOR PROCEDURE: %.*s\n", LIT(p->name)); - LLVMDumpValue(p->value); - gb_printf_err("\n\n\n\n"); - String filepath_ll = lb_filepath_ll_for_module(m); - if (LLVMPrintModuleToFile(m->mod, cast(char const *)filepath_ll.text, &llvm_error)) { - gb_printf_err("LLVM Error: %s\n", llvm_error); - } - LLVMVerifyFunction(p->value, LLVMPrintMessageAction); - exit_with_errors(); - } + lb_verify_function(m, p, true); } @@ -2468,69 +2979,24 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { code_mode = LLVMCodeModelKernel; } - String host_cpu_name = copy_string(permanent_allocator(), make_string_c(LLVMGetHostCPUName())); - String llvm_cpu = get_default_microarchitecture(); - char const *llvm_features = ""; - if (build_context.microarch.len != 0) { - if (build_context.microarch == "native") { - llvm_cpu = host_cpu_name; - } else { - llvm_cpu = copy_string(permanent_allocator(), build_context.microarch); - } - if (llvm_cpu == host_cpu_name) { - llvm_features = LLVMGetHostCPUFeatures(); + String llvm_cpu = get_final_microarchitecture(); + + gbString llvm_features = gb_string_make(temporary_allocator(), ""); + String_Iterator it = {build_context.target_features_string, 0}; + bool first = true; + for (;;) { + String str = string_split_iterator(&it, ','); + if (str == "") break; + if (!first) { + llvm_features = gb_string_appendc(llvm_features, ","); } + first = false; + + llvm_features = gb_string_appendc(llvm_features, "+"); + llvm_features = gb_string_append_length(llvm_features, str.text, str.len); } - // NOTE(Jeroen): Uncomment to get the list of supported microarchitectures. - /* - if (build_context.microarch == "?") { - string_set_add(&build_context.target_features_set, str_lit("+cpuhelp")); - } - */ - - if (build_context.target_features_set.entries.count != 0) { - // Prefix all of the features with a `+`, because we are - // enabling additional features. - char const *additional_features = target_features_set_to_cstring(permanent_allocator(), false, true); - - String f_string = make_string_c(llvm_features); - String a_string = make_string_c(additional_features); - isize f_len = f_string.len; - - if (f_len == 0) { - // The common case is that llvm_features is empty, so - // the target_features_set additions can be used as is. - llvm_features = additional_features; - } else { - // The user probably specified `-microarch:native`, so - // llvm_features is populated by LLVM's idea of what - // the host CPU supports. - // - // As far as I can tell, (which is barely better than - // wild guessing), a bitset is formed by parsing the - // string left to right. - // - // So, llvm_features + ',' + additonal_features, will - // makes the target_features_set override llvm_features. - - char *tmp = gb_alloc_array(permanent_allocator(), char, f_len + 1 + a_string.len + 1); - isize len = 0; - - // tmp = f_string - gb_memmove(tmp, f_string.text, f_string.len); - len += f_string.len; - // tmp += ',' - tmp[len++] = ','; - // tmp += a_string - gb_memmove(tmp + len, a_string.text, a_string.len); - len += a_string.len; - // tmp += NUL - tmp[len++] = 0; - - llvm_features = tmp; - } - } + debugf("CPU: %.*s, Features: %s\n", LIT(llvm_cpu), llvm_features); // GB_ASSERT_MSG(LLVMTargetHasAsmBackend(target)); @@ -2798,8 +3264,6 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { LLVMSetDLLStorageClass(g.value, LLVMDLLImportStorageClass); LLVMSetExternallyInitialized(g.value, true); lb_add_foreign_library_path(m, e->Variable.foreign_library); - - lb_set_wasm_import_attributes(g.value, e, name); } else { LLVMSetInitializer(g.value, LLVMConstNull(lb_type(m, e->type))); } @@ -2807,7 +3271,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { LLVMSetLinkage(g.value, LLVMDLLExportLinkage); LLVMSetDLLStorageClass(g.value, LLVMDLLExportStorageClass); } else if (!is_foreign) { - LLVMSetLinkage(g.value, USE_SEPARATE_MODULES ? LLVMExternalLinkage : LLVMInternalLinkage); + LLVMSetLinkage(g.value, USE_SEPARATE_MODULES ? LLVMWeakAnyLinkage : LLVMInternalLinkage); } lb_set_linkage_from_entity_flags(m, g.value, e->flags); @@ -2828,14 +3292,21 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { lbValue init = lb_const_value(m, tav.type, v); LLVMSetInitializer(g.value, init.value); var.is_initialized = true; + if (e->kind == Entity_Variable && e->Variable.is_rodata) { + LLVMSetGlobalConstant(g.value, true); + } } } } if (!var.is_initialized && is_type_untyped_nil(tav.type)) { var.is_initialized = true; + if (e->kind == Entity_Variable && e->Variable.is_rodata) { + LLVMSetGlobalConstant(g.value, true); + } } + } else if (e->kind == Entity_Variable && e->Variable.is_rodata) { + LLVMSetGlobalConstant(g.value, true); } - array_add(&global_variables, var); lb_add_entity(m, e, g); @@ -2970,7 +3441,22 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { TIME_SECTION("LLVM Add Foreign Library Paths"); lb_add_foreign_library_paths(gen); - TIME_SECTION("LLVM Object Generation"); + TIME_SECTION("LLVM Correct Entity Linkage"); + lb_correct_entity_linkage(gen); + + //////////////////////////////////////////// + for (auto const &entry: gen->modules) { + lbModule *m = entry.value; + if (!lb_is_module_empty(m)) { + gen->used_module_count += 1; + } + } + + gbString label_object_generation = gb_string_make(heap_allocator(), "LLVM Object Generation"); + if (gen->used_module_count > 1) { + label_object_generation = gb_string_append_fmt(label_object_generation, " (%td used modules)", gen->used_module_count); + } + TIME_SECTION_WITH_LEN(label_object_generation, gb_string_length(label_object_generation)); if (build_context.ignore_llvm_build) { gb_printf_err("LLVM object generation has been ignored!\n"); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index c4bf2691d..806864c7c 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -1,13 +1,9 @@ #if defined(GB_SYSTEM_WINDOWS) -#include "llvm-c/Core.h" -#include "llvm-c/ExecutionEngine.h" -#include "llvm-c/Target.h" -#include "llvm-c/Analysis.h" -#include "llvm-c/Object.h" -#include "llvm-c/BitWriter.h" -#include "llvm-c/DebugInfo.h" -#include "llvm-c/Transforms/PassBuilder.h" +#include #else +#include +#endif + #include #include #include @@ -26,7 +22,6 @@ #include #include #endif -#endif #if LLVM_VERSION_MAJOR < 11 #error "LLVM Version 11 is the minimum required" @@ -122,7 +117,6 @@ struct lbAddr { } swizzle_large; struct { Type *type; - i64 index; i64 bit_offset; i64 bit_size; } bitfield; @@ -140,11 +134,6 @@ enum lbFunctionPassManagerKind { lbFunctionPassManager_default, lbFunctionPassManager_default_without_memcpy, lbFunctionPassManager_none, - lbFunctionPassManager_minimal, - lbFunctionPassManager_size, - lbFunctionPassManager_speed, - lbFunctionPassManager_aggressive, - lbFunctionPassManager_COUNT }; @@ -158,6 +147,7 @@ struct lbModule { CheckerInfo *info; AstPackage *pkg; // possibly associated AstFile *file; // possibly associated + char const *module_name; PtrMap types; // mutex: types_mutex PtrMap struct_field_remapping; // Key: LLVMTypeRef or Type *, mutex: types_mutex @@ -187,7 +177,8 @@ struct lbModule { std::atomic nested_type_name_guid; Array procedures_to_generate; - Array global_procedures_and_types_to_create; + Array global_procedures_to_create; + Array global_types_to_create; lbProcedure *curr_procedure; @@ -210,6 +201,12 @@ struct lbModule { LLVMPassManagerRef function_pass_managers[lbFunctionPassManager_COUNT]; }; +struct lbEntityCorrection { + lbModule * other_module; + Entity * e; + char const *cname; +}; + struct lbGenerator : LinkerData { CheckerInfo *info; @@ -223,9 +220,13 @@ struct lbGenerator : LinkerData { std::atomic global_array_index; std::atomic global_generated_index; + isize used_module_count; + lbProcedure *startup_runtime; lbProcedure *cleanup_runtime; lbProcedure *objc_names; + + MPSCQueue entities_to_correct_linkage; }; @@ -304,6 +305,11 @@ enum lbProcedureFlag : u32 { lbProcedureFlag_DebugAllocaCopy = 1<<1, }; +struct lbVariadicReuseSlices { + Type *slice_type; + lbAddr slice_addr; +}; + struct lbProcedure { u32 flags; u16 state_flags; @@ -344,8 +350,10 @@ struct lbProcedure { bool in_multi_assignment; Array raw_input_parameters; - LLVMValueRef temp_callee_return_struct_memory; + Array variadic_reuses; + lbAddr variadic_reuse_base_array_ptr; + LLVMValueRef temp_callee_return_struct_memory; Ast *curr_stmt; Array scope_stack; @@ -372,7 +380,7 @@ struct lbProcedure { gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c); -gb_internal String lb_mangle_name(lbModule *m, Entity *e); +gb_internal String lb_mangle_name(Entity *e); gb_internal String lb_get_entity_name(lbModule *m, Entity *e, String name = {}); gb_internal LLVMAttributeRef lb_create_enum_attribute(LLVMContextRef ctx, char const *name, u64 value=0); @@ -582,6 +590,10 @@ gb_internal LLVMTypeRef llvm_array_type(LLVMTypeRef ElementType, uint64_t Elemen #endif } + +gb_internal void lb_set_metadata_custom_u64(lbModule *m, LLVMValueRef v_ref, String name, u64 value); +gb_internal u64 lb_get_metadata_custom_u64(lbModule *m, LLVMValueRef v_ref, String name); + #define LB_STARTUP_RUNTIME_PROC_NAME "__$startup_runtime" #define LB_CLEANUP_RUNTIME_PROC_NAME "__$cleanup_runtime" #define LB_TYPE_INFO_DATA_NAME "__$type_info_data" @@ -720,4 +732,4 @@ gb_global char const *llvm_linkage_strings[] = { "linker private weak linkage" }; -#define ODIN_METADATA_REQUIRE "odin-metadata-require", 21 +#define ODIN_METADATA_IS_PACKED str_lit("odin-is-packed") \ No newline at end of file diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 8035336d3..12bcc4e1f 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -94,9 +94,6 @@ gb_internal LLVMValueRef llvm_const_cast(LLVMValueRef val, LLVMTypeRef dst) { LLVMTypeKind kind = LLVMGetTypeKind(dst); switch (kind) { case LLVMPointerTypeKind: - if (LB_USE_NEW_PASS_SYSTEM) { - return val; - } return LLVMConstPointerCast(val, dst); case LLVMStructTypeKind: // GB_PANIC("%s -> %s", LLVMPrintValueToString(val), LLVMPrintTypeToString(dst)); @@ -341,6 +338,15 @@ gb_internal lbValue lb_emit_source_code_location_as_global_ptr(lbProcedure *p, S return addr.addr; } +gb_internal lbValue lb_const_source_code_location_as_global_ptr(lbModule *m, String const &procedure, TokenPos const &pos) { + lbValue loc = lb_const_source_code_location_const(m, procedure, pos); + lbAddr addr = lb_add_global_generated(m, loc.type, loc, nullptr); + lb_make_global_private_const(addr); + return addr.addr; +} + + + gb_internal lbValue lb_emit_source_code_location_as_global_ptr(lbProcedure *p, Ast *node) { lbValue loc = lb_emit_source_code_location_const(p, node); @@ -428,6 +434,8 @@ gb_internal LLVMValueRef lb_big_int_to_llvm(lbModule *m, Type *original_type, Bi } } + GB_ASSERT(!is_type_array(original_type)); + LLVMValueRef value = LLVMConstIntOfArbitraryPrecision(lb_type(m, original_type), cast(unsigned)((sz+7)/8), cast(u64 *)rop); if (big_int_is_neg(a)) { value = LLVMConstNeg(value); @@ -539,7 +547,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo LLVMValueRef ptr = LLVMBuildInBoundsGEP2(p->builder, llvm_type, array_data, indices, 2, ""); LLVMValueRef len = LLVMConstInt(lb_type(m, t_int), count, true); - lbAddr slice = lb_add_local_generated(p, type, false); + lbAddr slice = lb_add_local_generated(p, original_type, false); map_set(&m->exact_value_compound_literal_addr_map, value.value_compound, slice); lb_fill_slice(p, slice, {ptr, alloc_type_pointer(elem)}, {len, t_int}); diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index 2654a1d28..c896f889d 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -46,6 +46,15 @@ gb_internal LLVMMetadataRef lb_debug_end_location_from_ast(lbProcedure *p, Ast * return lb_debug_location_from_token_pos(p, ast_end_token(node).pos); } +gb_internal void lb_debug_file_line(lbModule *m, Ast *node, LLVMMetadataRef *file, unsigned *line) { + if (*file == nullptr) { + if (node) { + *file = lb_get_llvm_metadata(m, node->file()); + *line = cast(unsigned)ast_token(node).pos.line; + } + } +} + gb_internal LLVMMetadataRef lb_debug_type_internal_proc(lbModule *m, Type *type) { i64 size = type_size_of(type); // Check size gb_unused(size); @@ -117,6 +126,8 @@ gb_internal LLVMMetadataRef lb_debug_basic_struct(lbModule *m, String const &nam gb_internal LLVMMetadataRef lb_debug_struct(lbModule *m, Type *type, Type *bt, String name, LLVMMetadataRef scope, LLVMMetadataRef file, unsigned line) { GB_ASSERT(bt->kind == Type_Struct); + lb_debug_file_line(m, bt->Struct.node, &file, &line); + unsigned tag = DW_TAG_structure_type; if (is_type_raw_union(bt)) { tag = DW_TAG_union_type; @@ -336,6 +347,8 @@ gb_internal LLVMMetadataRef lb_debug_union(lbModule *m, Type *type, String name, Type *bt = base_type(type); GB_ASSERT(bt->kind == Type_Union); + lb_debug_file_line(m, bt->Union.node, &file, &line); + u64 size_in_bits = 8*type_size_of(bt); u32 align_in_bits = 8*cast(u32)type_align_of(bt); @@ -415,6 +428,8 @@ gb_internal LLVMMetadataRef lb_debug_bitset(lbModule *m, Type *type, String name Type *bt = base_type(type); GB_ASSERT(bt->kind == Type_BitSet); + lb_debug_file_line(m, bt->BitSet.node, &file, &line); + u64 size_in_bits = 8*type_size_of(bt); u32 align_in_bits = 8*cast(u32)type_align_of(bt); @@ -494,6 +509,8 @@ gb_internal LLVMMetadataRef lb_debug_enum(lbModule *m, Type *type, String name, Type *bt = base_type(type); GB_ASSERT(bt->kind == Type_Enum); + lb_debug_file_line(m, bt->Enum.node, &file, &line); + u64 size_in_bits = 8*type_size_of(bt); u32 align_in_bits = 8*cast(u32)type_align_of(bt); @@ -609,50 +626,50 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) { case Basic_complex32: { LLVMMetadataRef elements[2] = {}; - elements[0] = lb_debug_struct_field(m, str_lit("real"), t_f16, 0); - elements[1] = lb_debug_struct_field(m, str_lit("imag"), t_f16, 4); + elements[0] = lb_debug_struct_field(m, str_lit("real"), t_f16, 0*16); + elements[1] = lb_debug_struct_field(m, str_lit("imag"), t_f16, 1*16); return lb_debug_basic_struct(m, str_lit("complex32"), 64, 32, elements, gb_count_of(elements)); } case Basic_complex64: { LLVMMetadataRef elements[2] = {}; - elements[0] = lb_debug_struct_field(m, str_lit("real"), t_f32, 0); - elements[1] = lb_debug_struct_field(m, str_lit("imag"), t_f32, 4); + elements[0] = lb_debug_struct_field(m, str_lit("real"), t_f32, 0*32); + elements[1] = lb_debug_struct_field(m, str_lit("imag"), t_f32, 2*32); return lb_debug_basic_struct(m, str_lit("complex64"), 64, 32, elements, gb_count_of(elements)); } case Basic_complex128: { LLVMMetadataRef elements[2] = {}; - elements[0] = lb_debug_struct_field(m, str_lit("real"), t_f64, 0); - elements[1] = lb_debug_struct_field(m, str_lit("imag"), t_f64, 8); + elements[0] = lb_debug_struct_field(m, str_lit("real"), t_f64, 0*64); + elements[1] = lb_debug_struct_field(m, str_lit("imag"), t_f64, 1*64); return lb_debug_basic_struct(m, str_lit("complex128"), 128, 64, elements, gb_count_of(elements)); } case Basic_quaternion64: { LLVMMetadataRef elements[4] = {}; - elements[0] = lb_debug_struct_field(m, str_lit("imag"), t_f16, 0); - elements[1] = lb_debug_struct_field(m, str_lit("jmag"), t_f16, 4); - elements[2] = lb_debug_struct_field(m, str_lit("kmag"), t_f16, 8); - elements[3] = lb_debug_struct_field(m, str_lit("real"), t_f16, 12); + elements[0] = lb_debug_struct_field(m, str_lit("imag"), t_f16, 0*16); + elements[1] = lb_debug_struct_field(m, str_lit("jmag"), t_f16, 1*16); + elements[2] = lb_debug_struct_field(m, str_lit("kmag"), t_f16, 2*16); + elements[3] = lb_debug_struct_field(m, str_lit("real"), t_f16, 3*16); return lb_debug_basic_struct(m, str_lit("quaternion64"), 128, 32, elements, gb_count_of(elements)); } case Basic_quaternion128: { LLVMMetadataRef elements[4] = {}; - elements[0] = lb_debug_struct_field(m, str_lit("imag"), t_f32, 0); - elements[1] = lb_debug_struct_field(m, str_lit("jmag"), t_f32, 4); - elements[2] = lb_debug_struct_field(m, str_lit("kmag"), t_f32, 8); - elements[3] = lb_debug_struct_field(m, str_lit("real"), t_f32, 12); + elements[0] = lb_debug_struct_field(m, str_lit("imag"), t_f32, 0*32); + elements[1] = lb_debug_struct_field(m, str_lit("jmag"), t_f32, 1*32); + elements[2] = lb_debug_struct_field(m, str_lit("kmag"), t_f32, 2*32); + elements[3] = lb_debug_struct_field(m, str_lit("real"), t_f32, 3*32); return lb_debug_basic_struct(m, str_lit("quaternion128"), 128, 32, elements, gb_count_of(elements)); } case Basic_quaternion256: { LLVMMetadataRef elements[4] = {}; - elements[0] = lb_debug_struct_field(m, str_lit("imag"), t_f64, 0); - elements[1] = lb_debug_struct_field(m, str_lit("jmag"), t_f64, 8); - elements[2] = lb_debug_struct_field(m, str_lit("kmag"), t_f64, 16); - elements[3] = lb_debug_struct_field(m, str_lit("real"), t_f64, 24); + elements[0] = lb_debug_struct_field(m, str_lit("imag"), t_f64, 0*64); + elements[1] = lb_debug_struct_field(m, str_lit("jmag"), t_f64, 1*64); + elements[2] = lb_debug_struct_field(m, str_lit("kmag"), t_f64, 2*64); + elements[3] = lb_debug_struct_field(m, str_lit("real"), t_f64, 3*64); return lb_debug_basic_struct(m, str_lit("quaternion256"), 256, 32, elements, gb_count_of(elements)); } @@ -1170,6 +1187,7 @@ gb_internal void add_debug_info_for_global_constant_from_entity(lbGenerator *gen if (USE_SEPARATE_MODULES) { m = lb_module_of_entity(gen, e); } + GB_ASSERT(m != nullptr); if (is_type_integer(e->type)) { ExactValue const &value = e->Constant.value; diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index edd5daeca..4bb2676d1 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -296,12 +296,6 @@ gb_internal bool lb_try_direct_vector_arith(lbProcedure *p, TokenKind op, lbValu GB_ASSERT(vector_type0 == vector_type1); LLVMTypeRef vector_type = vector_type0; - LLVMValueRef lhs_vp = LLVMBuildPointerCast(p->builder, lhs_ptr.value, LLVMPointerType(vector_type, 0), ""); - LLVMValueRef rhs_vp = LLVMBuildPointerCast(p->builder, rhs_ptr.value, LLVMPointerType(vector_type, 0), ""); - LLVMValueRef x = LLVMBuildLoad2(p->builder, vector_type, lhs_vp, ""); - LLVMValueRef y = LLVMBuildLoad2(p->builder, vector_type, rhs_vp, ""); - LLVMValueRef z = nullptr; - Type *integral_type = base_type(elem_type); if (is_type_simd_vector(integral_type)) { integral_type = core_array_type(integral_type); @@ -311,8 +305,18 @@ gb_internal bool lb_try_direct_vector_arith(lbProcedure *p, TokenKind op, lbValu case Token_Add: op = Token_Or; break; case Token_Sub: op = Token_AndNot; break; } + Type *u = bit_set_to_int(type); + if (is_type_array(u)) { + return false; + } } + LLVMValueRef lhs_vp = LLVMBuildPointerCast(p->builder, lhs_ptr.value, LLVMPointerType(vector_type, 0), ""); + LLVMValueRef rhs_vp = LLVMBuildPointerCast(p->builder, rhs_ptr.value, LLVMPointerType(vector_type, 0), ""); + LLVMValueRef x = LLVMBuildLoad2(p->builder, vector_type, lhs_vp, ""); + LLVMValueRef y = LLVMBuildLoad2(p->builder, vector_type, rhs_vp, ""); + LLVMValueRef z = nullptr; + if (is_type_float(integral_type)) { switch (op) { case Token_Add: @@ -504,6 +508,10 @@ gb_internal bool lb_is_matrix_simdable(Type *t) { if ((mt->Matrix.row_count & 1) ^ (mt->Matrix.column_count & 1)) { return false; } + if (mt->Matrix.is_row_major) { + // TODO(bill): make #row_major matrices work with SIMD + return false; + } if (elem->kind == Type_Basic) { switch (elem->Basic.kind) { @@ -1123,12 +1131,21 @@ gb_internal lbValue lb_emit_arith(lbProcedure *p, TokenKind op, lbValue lhs, lbV switch (op) { case Token_Add: - real = lb_emit_arith(p, Token_Add, a, c, ft); - imag = lb_emit_arith(p, Token_Add, b, d, ft); - break; case Token_Sub: - real = lb_emit_arith(p, Token_Sub, a, c, ft); - imag = lb_emit_arith(p, Token_Sub, b, d, ft); + if (type_size_of(ft) == 2) { + a = lb_emit_conv(p, a, t_f32); + b = lb_emit_conv(p, b, t_f32); + c = lb_emit_conv(p, c, t_f32); + d = lb_emit_conv(p, d, t_f32); + real = lb_emit_arith(p, op, a, c, t_f32); + imag = lb_emit_arith(p, op, b, d, t_f32); + + real = lb_emit_conv(p, real, ft); + imag = lb_emit_conv(p, imag, ft); + } else { + real = lb_emit_arith(p, op, a, c, ft); + imag = lb_emit_arith(p, op, b, d, ft); + } break; case Token_Mul: { lbValue x = lb_emit_arith(p, Token_Mul, a, c, ft); @@ -1152,6 +1169,11 @@ gb_internal lbValue lb_emit_arith(lbProcedure *p, TokenKind op, lbValue lhs, lbV Type *ft = base_complex_elem_type(type); if (op == Token_Add || op == Token_Sub) { + Type *immediate_type = ft; + if (type_size_of(ft) == 2) { + immediate_type = t_f32; + } + lbAddr res = lb_add_local_generated(p, type, false); // NOTE: initialized in full later lbValue x0 = lb_emit_struct_ev(p, lhs, 0); lbValue x1 = lb_emit_struct_ev(p, lhs, 1); @@ -1163,15 +1185,39 @@ gb_internal lbValue lb_emit_arith(lbProcedure *p, TokenKind op, lbValue lhs, lbV lbValue y2 = lb_emit_struct_ev(p, rhs, 2); lbValue y3 = lb_emit_struct_ev(p, rhs, 3); - lbValue z0 = lb_emit_arith(p, op, x0, y0, ft); - lbValue z1 = lb_emit_arith(p, op, x1, y1, ft); - lbValue z2 = lb_emit_arith(p, op, x2, y2, ft); - lbValue z3 = lb_emit_arith(p, op, x3, y3, ft); + if (immediate_type != ft) { + x0 = lb_emit_conv(p, x0, immediate_type); + x1 = lb_emit_conv(p, x1, immediate_type); + x2 = lb_emit_conv(p, x2, immediate_type); + x3 = lb_emit_conv(p, x3, immediate_type); - lb_emit_store(p, lb_emit_struct_ep(p, res.addr, 0), z0); - lb_emit_store(p, lb_emit_struct_ep(p, res.addr, 1), z1); - lb_emit_store(p, lb_emit_struct_ep(p, res.addr, 2), z2); - lb_emit_store(p, lb_emit_struct_ep(p, res.addr, 3), z3); + y0 = lb_emit_conv(p, y0, immediate_type); + y1 = lb_emit_conv(p, y1, immediate_type); + y2 = lb_emit_conv(p, y2, immediate_type); + y3 = lb_emit_conv(p, y3, immediate_type); + } + + lbValue z0 = lb_emit_arith(p, op, x0, y0, immediate_type); + lbValue z1 = lb_emit_arith(p, op, x1, y1, immediate_type); + lbValue z2 = lb_emit_arith(p, op, x2, y2, immediate_type); + lbValue z3 = lb_emit_arith(p, op, x3, y3, immediate_type); + + lbValue d0 = lb_emit_struct_ep(p, res.addr, 0); + lbValue d1 = lb_emit_struct_ep(p, res.addr, 1); + lbValue d2 = lb_emit_struct_ep(p, res.addr, 2); + lbValue d3 = lb_emit_struct_ep(p, res.addr, 3); + + if (immediate_type != ft) { + d0 = lb_emit_conv(p, d0, ft); + d1 = lb_emit_conv(p, d1, ft); + d2 = lb_emit_conv(p, d2, ft); + d3 = lb_emit_conv(p, d3, ft); + } + + lb_emit_store(p, d0, z0); + lb_emit_store(p, d1, z1); + lb_emit_store(p, d2, z2); + lb_emit_store(p, d3, z3); return lb_addr_load(p, res); } else if (op == Token_Mul) { @@ -1244,6 +1290,14 @@ handle_op:; case Token_Add: op = Token_Or; break; case Token_Sub: op = Token_AndNot; break; } + Type *u = bit_set_to_int(type); + if (is_type_array(u)) { + lhs.type = u; + rhs.type = u; + res = lb_emit_arith(p, op, lhs, rhs, u); + res.type = type; + return res; + } } Type *integral_type = type; @@ -1399,6 +1453,7 @@ gb_internal lbValue lb_build_binary_in(lbProcedure *p, lbValue left, lbValue rig GB_ASSERT(are_types_identical(left.type, key_type)); Type *it = bit_set_to_int(rt); + left = lb_emit_conv(p, left, it); if (is_type_different_to_arch_endianness(it)) { left = lb_emit_byte_swap(p, left, integer_endian_type_to_platform_type(it)); @@ -1869,13 +1924,40 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { lbValue res_i128 = lb_emit_runtime_call(p, call, args); return lb_emit_conv(p, res_i128, t); } + i64 sz = type_size_of(src); lbValue res = {}; res.type = t; if (is_type_unsigned(dst)) { - res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t), ""); + switch (sz) { + case 2: + case 4: + res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t_u32), ""); + res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), false, ""); + break; + case 8: + res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t_u64), ""); + res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), false, ""); + break; + default: + GB_PANIC("Unhandled float type"); + break; + } } else { - res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t), ""); + switch (sz) { + case 2: + case 4: + res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t_i32), ""); + res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), true, ""); + break; + case 8: + res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t_i64), ""); + res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), true, ""); + break; + default: + GB_PANIC("Unhandled float type"); + break; + } } return res; } @@ -1985,6 +2067,26 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { } } + // bit_set <-> backing type + if (is_type_bit_set(src)) { + Type *backing = bit_set_to_int(src); + if (are_types_identical(backing, dst)) { + lbValue res = {}; + res.type = t; + res.value = value.value; + return res; + } + } + if (is_type_bit_set(dst)) { + Type *backing = bit_set_to_int(dst); + if (are_types_identical(src, backing)) { + lbValue res = {}; + res.type = t; + res.value = value.value; + return res; + } + } + // Pointer <-> uintptr if (is_type_pointer(src) && is_type_uintptr(dst)) { @@ -2013,13 +2115,6 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { } if (is_type_union(dst)) { - for (Type *vt : dst->Union.variants) { - if (are_types_identical(vt, src_type)) { - lbAddr parent = lb_add_local_generated(p, t, true); - lb_emit_store_union_variant(p, parent.addr, value, vt); - return lb_addr_load(p, parent); - } - } if (dst->Union.variants.count == 1) { Type *vt = dst->Union.variants[0]; if (internal_check_is_assignable_to(src_type, vt)) { @@ -2029,6 +2124,50 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { return lb_addr_load(p, parent); } } + for (Type *vt : dst->Union.variants) { + if (are_types_identical(src_type, vt)) { + lbAddr parent = lb_add_local_generated(p, t, true); + lb_emit_store_union_variant(p, parent.addr, value, vt); + return lb_addr_load(p, parent); + } + } + ValidIndexAndScore *valids = gb_alloc_array(temporary_allocator(), ValidIndexAndScore, dst->Union.variants.count); + isize valid_count = 0; + isize first_success_index = -1; + for_array(i, dst->Union.variants) { + Type *vt = dst->Union.variants[i]; + i64 score = 0; + if (internal_check_is_assignable_to(src_type, vt)) { + valids[valid_count].index = i; + valids[valid_count].score = score; + valid_count += 1; + if (first_success_index < 0) { + first_success_index = i; + } + } + } + if (valid_count > 1) { + gb_sort_array(valids, valid_count, valid_index_and_score_cmp); + i64 best_score = valids[0].score; + for (isize i = 1; i < valid_count; i++) { + auto v = valids[i]; + if (best_score > v.score) { + valid_count = i; + break; + } + best_score = v.score; + } + first_success_index = valids[0].index; + } + + if (valid_count == 1) { + Type *vt = dst->Union.variants[first_success_index]; + value = lb_emit_conv(p, value, vt); + lbAddr parent = lb_add_local_generated(p, t, true); + lb_emit_store_union_variant(p, parent.addr, value, vt); + return lb_addr_load(p, parent); + } + } // NOTE(bill): This has to be done before 'Pointer <-> Pointer' as it's @@ -2514,7 +2653,7 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left case Token_Lt: runtime_procedure = "cstring_lt"; break; case Token_Gt: runtime_procedure = "cstring_gt"; break; case Token_LtEq: runtime_procedure = "cstring_le"; break; - case Token_GtEq: runtime_procedure = "cstring_gt"; break; + case Token_GtEq: runtime_procedure = "cstring_ge"; break; } GB_ASSERT(runtime_procedure != nullptr); @@ -2537,7 +2676,7 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left case Token_Lt: runtime_procedure = "string_lt"; break; case Token_Gt: runtime_procedure = "string_gt"; break; case Token_LtEq: runtime_procedure = "string_le"; break; - case Token_GtEq: runtime_procedure = "string_gt"; break; + case Token_GtEq: runtime_procedure = "string_ge"; break; } GB_ASSERT(runtime_procedure != nullptr); @@ -2845,13 +2984,32 @@ gb_internal lbValue lb_emit_comp_against_nil(lbProcedure *p, TokenKind op_kind, case Type_Pointer: case Type_MultiPointer: case Type_Proc: - case Type_BitSet: if (op_kind == Token_CmpEq) { res.value = LLVMBuildIsNull(p->builder, x.value, ""); } else if (op_kind == Token_NotEq) { res.value = LLVMBuildIsNotNull(p->builder, x.value, ""); } return res; + case Type_BitSet: + { + Type *u = bit_set_to_int(bt); + if (is_type_array(u)) { + auto args = array_make(permanent_allocator(), 2); + lbValue lhs = lb_address_from_load_or_generate_local(p, x); + args[0] = lb_emit_conv(p, lhs, t_rawptr); + args[1] = lb_const_int(p->module, t_int, type_size_of(t)); + lbValue val = lb_emit_runtime_call(p, "memory_compare_zero", args); + lbValue res = lb_emit_comp(p, op_kind, val, lb_const_int(p->module, t_int, 0)); + return res; + } else { + if (op_kind == Token_CmpEq) { + res.value = LLVMBuildIsNull(p->builder, x.value, ""); + } else if (op_kind == Token_NotEq) { + res.value = LLVMBuildIsNotNull(p->builder, x.value, ""); + } + } + return res; + } case Type_Slice: { @@ -3252,13 +3410,8 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) { Type *type = type_of_expr(expr); GB_ASSERT_MSG(tv.mode != Addressing_Invalid, "invalid expression '%s' (tv.mode = %d, tv.type = %s) @ %s\n Current Proc: %.*s : %s", expr_to_string(expr), tv.mode, type_to_string(tv.type), token_pos_to_string(expr_pos), LIT(p->name), type_to_string(p->type)); - if (tv.value.kind != ExactValue_Invalid) { - // NOTE(bill): The commented out code below is just for debug purposes only - // if (is_type_untyped(type)) { - // gb_printf_err("%s %s : %s @ %p\n", token_pos_to_string(expr_pos), expr_to_string(expr), type_to_string(expr->tav.type), expr); - // GB_PANIC("%s\n", type_to_string(tv.type)); - // } + if (tv.value.kind != ExactValue_Invalid) { // NOTE(bill): Short on constant values return lb_const_value(p->module, type, tv.value); } else if (tv.mode == Addressing_Type) { @@ -3851,27 +4004,39 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { if (ie->expr->tav.mode == Addressing_SoaVariable) { // SOA Structures for slices/dynamic arrays - GB_ASSERT(is_type_pointer(type_of_expr(ie->expr))); + GB_ASSERT_MSG(is_type_multi_pointer(type_of_expr(ie->expr)), "%s", type_to_string(type_of_expr(ie->expr))); lbValue field = lb_build_expr(p, ie->expr); lbValue index = lb_build_expr(p, ie->index); - if (!build_context.no_bounds_check) { - // TODO HACK(bill): Clean up this hack to get the length for bounds checking - // GB_ASSERT(LLVMIsALoadInst(field.value)); + Ast *se_expr = unparen_expr(ie->expr); + if (se_expr->kind == Ast_SelectorExpr) { + ast_node(se, SelectorExpr, se_expr); + lbValue len = {}; - // lbValue a = {}; - // a.value = LLVMGetOperand(field.value, 0); - // a.type = alloc_type_pointer(field.type); + Type *type = base_type(type_deref(type_of_expr(se->expr))); + GB_ASSERT_MSG(is_type_soa_struct(type), "%s", type_to_string(type)); + if (type->Struct.soa_kind == StructSoa_Fixed) { + len = lb_const_int(p->module, t_int, type->Struct.soa_count); + } else { + lbAddr *found = map_get(&p->selector_addr, se_expr); + if (found) { + lbAddr addr = *found; + lbValue parent = lb_addr_get_ptr(p, addr); + if (is_type_pointer(type_deref(parent.type))) { + parent = lb_emit_load(p, parent); + } + len = lb_soa_struct_len(p, parent); + } + } - // irInstr *b = &a->Instr; - // GB_ASSERT(b->kind == irInstr_StructElementPtr); - // lbValue base_struct = b->StructElementPtr.address; - - // GB_ASSERT(is_type_soa_struct(type_deref(ir_type(base_struct)))); - // lbValue len = ir_soa_struct_len(p, base_struct); - // lb_emit_bounds_check(p, ast_token(ie->index), index, len); + if (len.value) { + lb_emit_bounds_check(p, ast_token(ie->index), index, len); + } + } else { + // TODO(bill): how do you even do bounds checking here? + } } lbValue val = lb_emit_ptr_offset(p, field, index); return lb_addr(val); @@ -4141,7 +4306,7 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { if (se->high == nullptr) { lbValue offset = base; LLVMValueRef indices[1] = {low.value}; - offset.value = LLVMBuildGEP2(p->builder, lb_type(p->module, offset.type->MultiPointer.elem), offset.value, indices, 1, ""); + offset.value = LLVMBuildGEP2(p->builder, lb_type(p->module, base_type(offset.type)->MultiPointer.elem), offset.value, indices, 1, ""); lb_addr_store(p, res, offset); } else { low = lb_emit_conv(p, low, t_int); @@ -4150,7 +4315,7 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { lb_emit_multi_pointer_slice_bounds_check(p, se->open, low, high); LLVMValueRef indices[1] = {low.value}; - LLVMValueRef ptr = LLVMBuildGEP2(p->builder, lb_type(p->module, base.type->MultiPointer.elem), base.value, indices, 1, ""); + LLVMValueRef ptr = LLVMBuildGEP2(p->builder, lb_type(p->module, base_type(base.type)->MultiPointer.elem), base.value, indices, 1, ""); LLVMValueRef len = LLVMBuildSub(p->builder, high.value, low.value, ""); LLVMValueRef gep0 = lb_emit_struct_ep(p, res.addr, 0).value; @@ -4218,6 +4383,7 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { lbValue field_dst = lb_emit_struct_ep(p, dst.addr, i); lbValue field_src = lb_emit_struct_ep(p, lb_addr_get_ptr(p, addr), i); field_src = lb_emit_array_ep(p, field_src, low); + field_src = lb_emit_conv(p, field_src, type_deref(field_dst.type)); lb_emit_store(p, field_dst, field_src); } @@ -4233,6 +4399,7 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { lbValue field_dst = lb_emit_struct_ep(p, dst.addr, i); lbValue field_src = lb_emit_struct_ev(p, base, i); field_src = lb_emit_ptr_offset(p, field_src, low); + field_src = lb_emit_conv(p, field_src, type_deref(field_dst.type)); lb_emit_store(p, field_dst, field_src); } @@ -4247,6 +4414,7 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { lbValue field_dst = lb_emit_struct_ep(p, dst.addr, i); lbValue field_src = lb_emit_struct_ev(p, base, i); field_src = lb_emit_ptr_offset(p, field_src, low); + field_src = lb_emit_conv(p, field_src, type_deref(field_dst.type)); lb_emit_store(p, field_dst, field_src); } @@ -4296,7 +4464,19 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { switch (bt->kind) { default: GB_PANIC("Unknown CompoundLit type: %s", type_to_string(type)); break; - case Type_BitField: + case Type_BitField: { + TEMPORARY_ALLOCATOR_GUARD(); + + // Type *backing_type = core_type(bt->BitField.backing_type); + + struct FieldData { + Type *field_type; + u64 bit_offset; + u64 bit_size; + }; + auto values = array_make(temporary_allocator(), 0, cl->elems.count); + auto fields = array_make(temporary_allocator(), 0, cl->elems.count); + for (Ast *elem : cl->elems) { ast_node(fv, FieldValue, elem); String name = fv->field->Ident.token.string; @@ -4307,26 +4487,176 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { GB_ASSERT(sel.entity != nullptr); i64 index = sel.index[0]; - i64 bit_offset = 0; - i64 bit_size = -1; - for_array(i, bt->BitField.fields) { - Entity *f = bt->BitField.fields[i]; - if (f == sel.entity) { - bit_offset = bt->BitField.bit_offsets[i]; - bit_size = bt->BitField.bit_sizes[i]; - break; - } - } + Entity *f = bt->BitField.fields[index]; + GB_ASSERT(f == sel.entity); + i64 bit_offset = bt->BitField.bit_offsets[index]; + i64 bit_size = bt->BitField.bit_sizes[index]; GB_ASSERT(bit_size > 0); Type *field_type = sel.entity->type; lbValue field_expr = lb_build_expr(p, fv->value); field_expr = lb_emit_conv(p, field_expr, field_type); - - lbAddr field_addr = lb_addr_bit_field(v.addr, field_type, index, bit_offset, bit_size); - lb_addr_store(p, field_addr, field_expr); + array_add(&values, field_expr); + array_add(&fields, FieldData{field_type, cast(u64)bit_offset, cast(u64)bit_size}); } + + // NOTE(bill): inline insertion sort should be good enough, right? + for (isize i = 1; i < values.count; i++) { + for (isize j = i; + j > 0 && fields[i].bit_offset < fields[j].bit_offset; + j--) { + auto vtmp = values[j]; + values[j] = values[j-1]; + values[j-1] = vtmp; + + auto ftmp = fields[j]; + fields[j] = fields[j-1]; + fields[j-1] = ftmp; + } + } + + bool any_fields_different_endian = false; + for (auto const &f : fields) { + if (is_type_different_to_arch_endianness(f.field_type)) { + // NOTE(bill): Just be slow for this, to be correct + any_fields_different_endian = true; + break; + } + } + + if (!any_fields_different_endian && + fields.count == bt->BitField.fields.count) { + // SINGLE INTEGER BACKING ONLY + + Type *backing_type = core_type(bt->BitField.backing_type); + GB_ASSERT(is_type_integer(backing_type) || + (is_type_array(backing_type) && is_type_integer(backing_type->Array.elem))); + + // NOTE(bill): all fields are present + // this means no masking is necessary since on write, the bits will be overridden + + lbValue dst_byte_ptr = lb_emit_conv(p, v.addr, t_u8_ptr); + u64 total_bit_size = cast(u64)(8*type_size_of(bt)); + + if (is_type_integer(backing_type)) { + LLVMTypeRef lit = lb_type(p->module, backing_type); + + LLVMValueRef res = LLVMConstInt(lit, 0, false); + + for (isize i = 0; i < fields.count; i++) { + auto const &f = fields[i]; + + LLVMValueRef mask = LLVMConstInt(lit, 1, false); + mask = LLVMConstShl(mask, LLVMConstInt(lit, f.bit_size, false)); + mask = LLVMConstSub(mask, LLVMConstInt(lit, 1, false)); + + LLVMValueRef elem = values[i].value; + if (lb_sizeof(lit) < lb_sizeof(LLVMTypeOf(elem))) { + elem = LLVMBuildTrunc(p->builder, elem, lit, ""); + } else { + elem = LLVMBuildZExt(p->builder, elem, lit, ""); + } + elem = LLVMBuildAnd(p->builder, elem, mask, ""); + + elem = LLVMBuildShl(p->builder, elem, LLVMConstInt(lit, f.bit_offset, false), ""); + + res = LLVMBuildOr(p->builder, res, elem, ""); + } + + LLVMBuildStore(p->builder, res, v.addr.value); + } else if (is_type_array(backing_type)) { + // ARRAY OF INTEGER BACKING + + i64 array_count = backing_type->Array.count; + LLVMTypeRef lit = lb_type(p->module, core_type(backing_type->Array.elem)); + gb_unused(array_count); + gb_unused(lit); + + LLVMValueRef *elems = gb_alloc_array(temporary_allocator(), LLVMValueRef, array_count); + for (i64 i = 0; i < array_count; i++) { + elems[i] = LLVMConstInt(lit, 0, false); + } + + u64 elem_bit_size = cast(u64)(8*type_size_of(backing_type->Array.elem)); + u64 curr_bit_offset = 0; + for (isize i = 0; i < fields.count; i++) { + auto const &f = fields[i]; + + LLVMValueRef val = values[i].value; + LLVMTypeRef vt = lb_type(p->module, values[i].type); + for (u64 bits_to_set = f.bit_size; + bits_to_set > 0; + /**/) { + i64 elem_idx = curr_bit_offset/elem_bit_size; + u64 elem_bit_offset = curr_bit_offset%elem_bit_size; + + u64 mask_width = gb_min(bits_to_set, elem_bit_size-elem_bit_offset); + GB_ASSERT(mask_width > 0); + bits_to_set -= mask_width; + + LLVMValueRef mask = LLVMConstInt(vt, 1, false); + mask = LLVMConstShl(mask, LLVMConstInt(vt, mask_width, false)); + mask = LLVMConstSub(mask, LLVMConstInt(vt, 1, false)); + + LLVMValueRef to_set = LLVMBuildAnd(p->builder, val, mask, ""); + + if (elem_bit_offset != 0) { + to_set = LLVMBuildShl(p->builder, to_set, LLVMConstInt(vt, elem_bit_offset, false), ""); + } + to_set = LLVMBuildTrunc(p->builder, to_set, lit, ""); + + if (LLVMIsNull(elems[elem_idx])) { + elems[elem_idx] = to_set; // don't even bother doing `0 | to_set` + } else { + elems[elem_idx] = LLVMBuildOr(p->builder, elems[elem_idx], to_set, ""); + } + + if (mask_width != 0) { + val = LLVMBuildLShr(p->builder, val, LLVMConstInt(vt, mask_width, false), ""); + } + curr_bit_offset += mask_width; + } + + GB_ASSERT(curr_bit_offset == f.bit_offset + f.bit_size); + } + + for (i64 i = 0; i < array_count; i++) { + LLVMValueRef elem_ptr = LLVMBuildStructGEP2(p->builder, lb_type(p->module, backing_type), v.addr.value, cast(unsigned)i, ""); + LLVMBuildStore(p->builder, elems[i], elem_ptr); + } + } else { + // SLOW STORAGE + + for_array(i, fields) { + auto const &f = fields[i]; + + if ((f.bit_offset & 7) == 0) { + u64 unpacked_bit_size = cast(u64)(8*type_size_of(f.field_type)); + u64 byte_size = (f.bit_size+7)/8; + + if (f.bit_offset + unpacked_bit_size <= total_bit_size) { + byte_size = unpacked_bit_size/8; + } + lbValue dst = lb_emit_ptr_offset(p, dst_byte_ptr, lb_const_int(p->module, t_int, f.bit_offset/8)); + lbValue src = lb_address_from_load_or_generate_local(p, values[i]); + lb_mem_copy_non_overlapping(p, dst, src, lb_const_int(p->module, t_uintptr, byte_size)); + } else { + lbAddr dst = lb_addr_bit_field(v.addr, f.field_type, f.bit_offset, f.bit_size); + lb_addr_store(p, dst, values[i]); + } + } + } + } else { + // individual storing + for_array(i, values) { + auto const &f = fields[i]; + lbAddr dst = lb_addr_bit_field(v.addr, f.field_type, f.bit_offset, f.bit_size); + lb_addr_store(p, dst, values[i]); + } + } + return v; + } case Type_Struct: { // TODO(bill): "constant" '#raw_union's are not initialized constantly at the moment. @@ -4356,10 +4686,26 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { if (lb_is_nested_possibly_constant(type, sel, elem)) { continue; } - lbValue dst = lb_emit_deep_field_gep(p, comp_lit_ptr, sel); field_expr = lb_build_expr(p, elem); field_expr = lb_emit_conv(p, field_expr, sel.entity->type); - lb_emit_store(p, dst, field_expr); + if (sel.is_bit_field) { + Selection sub_sel = trim_selection(sel); + lbValue trimmed_dst = lb_emit_deep_field_gep(p, comp_lit_ptr, sub_sel); + Type *bf = base_type(type_deref(trimmed_dst.type)); + if (is_type_pointer(bf)) { + trimmed_dst = lb_emit_load(p, trimmed_dst); + bf = base_type(type_deref(trimmed_dst.type)); + } + GB_ASSERT(bf->kind == Type_BitField); + + isize idx = sel.index[sel.index.count-1]; + lbAddr dst = lb_addr_bit_field(trimmed_dst, bf->BitField.fields[idx]->type, bf->BitField.bit_offsets[idx], bf->BitField.bit_sizes[idx]); + lb_addr_store(p, dst, field_expr); + + } else { + lbValue dst = lb_emit_deep_field_gep(p, comp_lit_ptr, sel); + lb_emit_store(p, dst, field_expr); + } continue; } @@ -4584,29 +4930,43 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { case Type_BitSet: { i64 sz = type_size_of(type); if (cl->elems.count > 0 && sz > 0) { - lb_addr_store(p, v, lb_const_value(p->module, type, exact_value_compound(expr))); - lbValue lower = lb_const_value(p->module, t_int, exact_value_i64(bt->BitSet.lower)); - for (Ast *elem : cl->elems) { - GB_ASSERT(elem->kind != Ast_FieldValue); - if (lb_is_elem_const(elem, et)) { - continue; + Type *backing = bit_set_to_int(type); + if (is_type_array(backing)) { + GB_PANIC("TODO: bit_set [N]T"); + Type *base_it = core_array_type(backing); + i64 bits_per_elem = 8*type_size_of(base_it); + gb_unused(bits_per_elem); + lbValue one = lb_const_value(p->module, t_i64, exact_value_i64(1)); + for (Ast *elem : cl->elems) { + GB_ASSERT(elem->kind != Ast_FieldValue); + lbValue expr = lb_build_expr(p, elem); + GB_ASSERT(expr.type->kind != Type_Tuple); + + lbValue e = lb_emit_conv(p, expr, t_i64); + e = lb_emit_arith(p, Token_Sub, e, lower, t_i64); + // lbValue idx = lb_emit_arith(p, Token_Div, e, bits_per_elem, t_i64); + // lbValue val = lb_emit_arith(p, Token_Div, e, bits_per_elem, t_i64); } - - lbValue expr = lb_build_expr(p, elem); - GB_ASSERT(expr.type->kind != Type_Tuple); - + } else { Type *it = bit_set_to_int(bt); lbValue one = lb_const_value(p->module, it, exact_value_i64(1)); - lbValue e = lb_emit_conv(p, expr, it); - e = lb_emit_arith(p, Token_Sub, e, lower, it); - e = lb_emit_arith(p, Token_Shl, one, e, it); + for (Ast *elem : cl->elems) { + GB_ASSERT(elem->kind != Ast_FieldValue); - lbValue old_value = lb_emit_transmute(p, lb_addr_load(p, v), it); - lbValue new_value = lb_emit_arith(p, Token_Or, old_value, e, it); - new_value = lb_emit_transmute(p, new_value, type); - lb_addr_store(p, v, new_value); + lbValue expr = lb_build_expr(p, elem); + GB_ASSERT(expr.type->kind != Type_Tuple); + + lbValue e = lb_emit_conv(p, expr, it); + e = lb_emit_arith(p, Token_Sub, e, lower, it); + e = lb_emit_arith(p, Token_Shl, one, e, it); + + lbValue old_value = lb_emit_transmute(p, lb_addr_load(p, v), it); + lbValue new_value = lb_emit_arith(p, Token_Or, old_value, e, it); + new_value = lb_emit_transmute(p, new_value, type); + lb_addr_store(p, v, new_value); + } } } break; @@ -4771,7 +5131,7 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { u8 bit_size = bf_type->BitField.bit_sizes[index]; i64 bit_offset = bf_type->BitField.bit_offsets[index]; - return lb_addr_bit_field(ptr, f->type, index, bit_offset, bit_size); + return lb_addr_bit_field(ptr, f->type, bit_offset, bit_size); } { @@ -4816,6 +5176,9 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { if (sub_sel.index.count > 0) { item = lb_emit_deep_field_gep(p, item, sub_sel); } + // make sure it's ^T and not [^]T + item.type = alloc_type_multi_pointer_to_pointer(item.type); + return lb_addr(item); } else if (addr.kind == lbAddr_Swizzle) { GB_ASSERT(sel.index.count > 0); @@ -4827,6 +5190,11 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { sel.index[0] = addr.swizzle.indices[sel.index[0]]; } + Type *atype = type_deref(lb_addr_type(addr)); + if (is_type_soa_struct(atype)) { + map_set(&p->selector_addr, expr, addr); + } + lbValue a = lb_addr_get_ptr(p, addr); a = lb_emit_deep_field_gep(p, a, sel); return lb_addr(a); @@ -5032,6 +5400,54 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { lbValue ptr = lb_address_from_load_or_generate_local(p, lb_build_expr(p, expr)); return lb_addr(ptr); case_end; + + + case_ast_node(be, OrBranchExpr, expr); + lbBlock *block = nullptr; + + if (be->label != nullptr) { + lbBranchBlocks bb = lb_lookup_branch_blocks(p, be->label); + switch (be->token.kind) { + case Token_or_break: block = bb.break_; break; + case Token_or_continue: block = bb.continue_; break; + } + } else { + for (lbTargetList *t = p->target_list; t != nullptr && block == nullptr; t = t->prev) { + if (t->is_block) { + continue; + } + + switch (be->token.kind) { + case Token_or_break: block = t->break_; break; + case Token_or_continue: block = t->continue_; break; + } + } + } + + GB_ASSERT(block != nullptr); + TypeAndValue tv = expr->tav; + + lbValue lhs = {}; + lbValue rhs = {}; + lb_emit_try_lhs_rhs(p, be->expr, tv, &lhs, &rhs); + Type *type = default_type(tv.type); + if (lhs.value) { + lhs = lb_emit_conv(p, lhs, type); + } else if (type != nullptr && type != t_invalid) { + lhs = lb_const_nil(p->module, type); + } + + lbBlock *then = lb_create_block(p, "or_branch.then"); + lbBlock *else_ = lb_create_block(p, "or_branch.else"); + + lb_emit_if(p, lb_emit_try_has_value(p, rhs), then, else_); + lb_start_block(p, else_); + lb_emit_defer_stmts(p, lbDeferExit_Branch, block); + lb_emit_jump(p, block); + lb_start_block(p, then); + + return lb_addr(lb_address_from_load_or_generate_local(p, lhs)); + case_end; } TokenPos token_pos = ast_token(expr).pos; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index da69f94d7..a91c1d1fe 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -29,8 +29,9 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { module_name = gb_string_appendc(module_name, "-builtin"); } + m->module_name = module_name ? module_name : "odin_package"; m->ctx = LLVMContextCreate(); - m->mod = LLVMModuleCreateWithNameInContext(module_name ? module_name : "odin_package", m->ctx); + m->mod = LLVMModuleCreateWithNameInContext(m->module_name, m->ctx); // m->debug_builder = nullptr; if (build_context.ODIN_DEBUG) { enum {DEBUG_METADATA_VERSION = 3}; @@ -71,14 +72,15 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { map_init(&m->hasher_procs); map_init(&m->map_get_procs); map_init(&m->map_set_procs); - if (build_context.use_separate_modules) { + if (USE_SEPARATE_MODULES) { array_init(&m->procedures_to_generate, a, 0, 1<<10); map_init(&m->procedure_values, 1<<11); } else { array_init(&m->procedures_to_generate, a, 0, c->info.all_procedures.count); map_init(&m->procedure_values, c->info.all_procedures.count*2); } - array_init(&m->global_procedures_and_types_to_create, a, 0, 1024); + array_init(&m->global_procedures_to_create, a, 0, 1024); + array_init(&m->global_types_to_create, a, 0, 1024); array_init(&m->missing_procedures_to_check, a, 0, 16); map_init(&m->debug_values); @@ -117,15 +119,17 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { map_init(&gen->anonymous_proc_lits, 1024); if (USE_SEPARATE_MODULES) { + bool module_per_file = build_context.module_per_file && build_context.optimization_level <= 0; for (auto const &entry : gen->info->packages) { AstPackage *pkg = entry.value; - #if 1 auto m = gb_alloc_item(permanent_allocator(), lbModule); m->pkg = pkg; m->gen = gen; map_set(&gen->modules, cast(void *)pkg, m); lb_init_module(m, c); - #else + if (!module_per_file) { + continue; + } // NOTE(bill): Probably per file is not a good idea, so leave this for later for (AstFile *file : pkg->files) { auto m = gb_alloc_item(permanent_allocator(), lbModule); @@ -135,21 +139,21 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { map_set(&gen->modules, cast(void *)file, m); lb_init_module(m, c); } - #endif } } gen->default_module.gen = gen; - map_set(&gen->modules, cast(void *)nullptr, &gen->default_module); + map_set(&gen->modules, cast(void *)1, &gen->default_module); lb_init_module(&gen->default_module, c); - for (auto const &entry : gen->modules) { lbModule *m = entry.value; LLVMContextRef ctx = LLVMGetModuleContext(m->mod); map_set(&gen->modules_through_ctx, ctx, m); } + mpsc_init(&gen->entities_to_correct_linkage, heap_allocator()); + return true; } @@ -386,12 +390,14 @@ gb_internal lbModule *lb_module_of_entity(lbGenerator *gen, Entity *e) { if (e->file) { found = map_get(&gen->modules, cast(void *)e->file); if (found) { + GB_ASSERT(*found != nullptr); return *found; } } if (e->pkg) { found = map_get(&gen->modules, cast(void *)e->pkg); if (found) { + GB_ASSERT(*found != nullptr); return *found; } } @@ -450,14 +456,13 @@ gb_internal lbAddr lb_addr_swizzle_large(lbValue addr, Type *array_type, Slicebuilder, + shifted_value.value, + LLVMConstInt(LLVMTypeOf(shifted_value.value), shift_amount, false), ""); - auto args = array_make(temporary_allocator(), 4); - args[0] = dst; - args[1] = lb_address_from_load_or_generate_local(p, value); - args[2] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset); - args[3] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size); - lb_emit_runtime_call(p, "__write_bits", args); + lbValue src = lb_address_from_load_or_generate_local(p, shifted_value); + + auto args = array_make(temporary_allocator(), 4); + args[0] = dst; + args[1] = src; + args[2] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset); + args[3] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size); + lb_emit_runtime_call(p, "__write_bits", args); + } else if ((addr.bitfield.bit_offset % 8) == 0 && + (addr.bitfield.bit_size % 8) == 0) { + lbValue src = lb_address_from_load_or_generate_local(p, value); + + lbValue byte_offset = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset/8); + lbValue byte_size = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size/8); + lbValue dst_offset = lb_emit_conv(p, dst, t_u8_ptr); + dst_offset = lb_emit_ptr_offset(p, dst_offset, byte_offset); + lb_mem_copy_non_overlapping(p, dst_offset, src, byte_size); + } else { + lbValue src = lb_address_from_load_or_generate_local(p, value); + + auto args = array_make(temporary_allocator(), 4); + args[0] = dst; + args[1] = src; + args[2] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset); + args[3] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size); + lb_emit_runtime_call(p, "__write_bits", args); + } return; } else if (addr.kind == lbAddr_RelativePointer) { Type *rel_ptr = base_type(lb_addr_type(addr)); @@ -984,13 +1016,15 @@ gb_internal void lb_emit_store(lbProcedure *p, lbValue ptr, lbValue value) { return; } - Type *a = type_deref(ptr.type); + Type *a = type_deref(ptr.type, true); if (LLVMIsNull(value.value)) { LLVMTypeRef src_t = llvm_addr_type(p->module, ptr); if (is_type_proc(a)) { LLVMTypeRef rawptr_type = lb_type(p->module, t_rawptr); LLVMTypeRef rawptr_ptr_type = LLVMPointerType(rawptr_type, 0); LLVMBuildStore(p->builder, LLVMConstNull(rawptr_type), LLVMBuildBitCast(p->builder, ptr.value, rawptr_ptr_type, "")); + } else if (is_type_bit_set(a)) { + lb_mem_zero_ptr(p, ptr.value, a, 1); } else if (lb_sizeof(src_t) <= lb_max_zero_init_size()) { LLVMBuildStore(p->builder, LLVMConstNull(src_t), ptr.value); } else { @@ -1078,9 +1112,19 @@ gb_internal lbValue lb_emit_load(lbProcedure *p, lbValue value) { return lb_addr_load(p, addr); } - GB_ASSERT(is_type_pointer(value.type)); + GB_ASSERT_MSG(is_type_pointer(value.type), "%s", type_to_string(value.type)); Type *t = type_deref(value.type); LLVMValueRef v = LLVMBuildLoad2(p->builder, lb_type(p->module, t), value.value, ""); + + // If it is not an instruction it isn't a GEP, so we don't need to track alignment in the metadata, + // which is not possible anyway (only LLVM instructions can have metadata). + if (LLVMIsAInstruction(value.value)) { + u64 is_packed = lb_get_metadata_custom_u64(p->module, value.value, ODIN_METADATA_IS_PACKED); + if (is_packed != 0) { + LLVMSetAlignment(v, 1); + } + } + return lbValue{v, t}; } @@ -1088,23 +1132,77 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { GB_ASSERT(addr.addr.value != nullptr); if (addr.kind == lbAddr_BitField) { + Type *ct = core_type(addr.bitfield.type); + bool do_mask = false; + if (is_type_unsigned(ct) || is_type_boolean(ct)) { + // Mask + if (addr.bitfield.bit_size != 8*type_size_of(ct)) { + do_mask = true; + } + } + + i64 total_bitfield_bit_size = 8*type_size_of(lb_addr_type(addr)); + i64 dst_byte_size = type_size_of(addr.bitfield.type); lbAddr dst = lb_add_local_generated(p, addr.bitfield.type, true); lbValue src = addr.addr; - auto args = array_make(temporary_allocator(), 4); - args[0] = dst.addr; - args[1] = src; - args[2] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset); - args[3] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size); - lb_emit_runtime_call(p, "__read_bits", args); + lbValue bit_offset = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset); + lbValue bit_size = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size); + lbValue byte_offset = lb_const_int(p->module, t_uintptr, (addr.bitfield.bit_offset+7)/8); + lbValue byte_size = lb_const_int(p->module, t_uintptr, (addr.bitfield.bit_size+7)/8); - lbValue r = lb_addr_load(p, dst); + GB_ASSERT(type_size_of(addr.bitfield.type) >= ((addr.bitfield.bit_size+7)/8)); - if (!is_type_unsigned(core_type(addr.bitfield.type))) { + lbValue r = {}; + if (is_type_endian_big(addr.bitfield.type)) { + auto args = array_make(temporary_allocator(), 4); + args[0] = dst.addr; + args[1] = src; + args[2] = bit_offset; + args[3] = bit_size; + lb_emit_runtime_call(p, "__read_bits", args); + + LLVMValueRef shift_amount = LLVMConstInt( + lb_type(p->module, lb_addr_type(dst)), + 8*dst_byte_size - addr.bitfield.bit_size, + false + ); + r = lb_addr_load(p, dst); + r.value = LLVMBuildShl(p->builder, r.value, shift_amount, ""); + } else if ((addr.bitfield.bit_offset % 8) == 0) { + do_mask = 8*dst_byte_size != addr.bitfield.bit_size; + + lbValue copy_size = byte_size; + lbValue src_offset = lb_emit_conv(p, src, t_u8_ptr); + src_offset = lb_emit_ptr_offset(p, src_offset, byte_offset); + if (addr.bitfield.bit_offset + 8*dst_byte_size <= total_bitfield_bit_size) { + copy_size = lb_const_int(p->module, t_uintptr, dst_byte_size); + } + lb_mem_copy_non_overlapping(p, dst.addr, src_offset, copy_size, false); + r = lb_addr_load(p, dst); + } else { + auto args = array_make(temporary_allocator(), 4); + args[0] = dst.addr; + args[1] = src; + args[2] = bit_offset; + args[3] = bit_size; + lb_emit_runtime_call(p, "__read_bits", args); + r = lb_addr_load(p, dst); + } + + Type *t = addr.bitfield.type; + + if (do_mask) { + GB_ASSERT(addr.bitfield.bit_size <= 8*type_size_of(ct)); + + lbValue mask = lb_const_int(p->module, t, (1ull<module, t, 1ull<<(addr.bitfield.bit_size-1)); r = lb_emit_arith(p, Token_Xor, r, m, t); r = lb_emit_arith(p, Token_Sub, r, m, t); @@ -1239,7 +1337,7 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { for (isize i = 0; i < field_count; i++) { Entity *field = t->Struct.fields[i]; Type *base_type = field->type; - GB_ASSERT(base_type->kind == Type_Pointer); + GB_ASSERT(base_type->kind == Type_MultiPointer); lbValue dst = lb_emit_struct_ep(p, res.addr, cast(i32)i); lbValue src_ptr = lb_emit_struct_ep(p, addr.addr, cast(i32)i); @@ -1298,8 +1396,6 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { LLVMTypeRef vector_type = nullptr; if (lb_try_vector_cast(p->module, addr.addr, &vector_type)) { - LLVMSetAlignment(res.addr.value, cast(unsigned)lb_alignof(vector_type)); - LLVMValueRef vp = LLVMBuildPointerCast(p->builder, addr.addr.value, LLVMPointerType(vector_type, 0), ""); LLVMValueRef v = LLVMBuildLoad2(p->builder, vector_type, vp, ""); LLVMValueRef scalars[4] = {}; @@ -1309,6 +1405,8 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { LLVMValueRef mask = LLVMConstVector(scalars, addr.swizzle.count); LLVMValueRef sv = llvm_basic_shuffle(p, v, mask); + LLVMSetAlignment(res.addr.value, cast(unsigned)lb_alignof(LLVMTypeOf(sv))); + LLVMValueRef dst = LLVMBuildPointerCast(p->builder, ptr.value, LLVMPointerType(LLVMTypeOf(sv), 0), ""); LLVMBuildStore(p->builder, sv, dst); } else { @@ -1439,7 +1537,7 @@ gb_internal void lb_clone_struct_type(LLVMTypeRef dst, LLVMTypeRef src) { LLVMStructSetBody(dst, fields, field_count, LLVMIsPackedStruct(src)); } -gb_internal String lb_mangle_name(lbModule *m, Entity *e) { +gb_internal String lb_mangle_name(Entity *e) { String name = e->token.string; AstPackage *pkg = e->pkg; @@ -1539,6 +1637,7 @@ gb_internal String lb_set_nested_type_name_ir_mangled_name(Entity *e, lbProcedur } gb_internal String lb_get_entity_name(lbModule *m, Entity *e, String default_name) { + GB_ASSERT(m != nullptr); if (e != nullptr && e->kind == Entity_TypeName && e->TypeName.ir_mangled_name.len != 0) { return e->TypeName.ir_mangled_name; } @@ -1570,7 +1669,7 @@ gb_internal String lb_get_entity_name(lbModule *m, Entity *e, String default_nam } if (!no_name_mangle) { - name = lb_mangle_name(m, e); + name = lb_mangle_name(e); } if (name.len == 0) { name = e->token.string; @@ -2433,6 +2532,12 @@ gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, cha gb_internal void lb_add_attribute_to_proc(lbModule *m, LLVMValueRef proc_value, char const *name, u64 value=0) { LLVMAddAttributeAtIndex(proc_value, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(m->ctx, name, value)); } + +gb_internal bool lb_proc_has_attribute(lbModule *m, LLVMValueRef proc_value, char const *name) { + LLVMAttributeRef ref = LLVMGetEnumAttributeAtIndex(proc_value, LLVMAttributeIndex_FunctionIndex, LLVMGetEnumAttributeKindForName(name, gb_strlen(name))); + return ref != nullptr; +} + gb_internal void lb_add_attribute_to_proc_with_string(lbModule *m, LLVMValueRef proc_value, String const &name, String const &value) { LLVMAttributeRef attr = lb_create_string_attribute(m->ctx, name, value); LLVMAddAttributeAtIndex(proc_value, LLVMAttributeIndex_FunctionIndex, attr); @@ -2948,7 +3053,7 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) { if (e->code_gen_module != nullptr) { other_module = e->code_gen_module; } else { - other_module = nullptr; + other_module = &m->gen->default_module; } is_external = other_module != m; } @@ -2966,8 +3071,6 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) { lb_set_entity_from_other_modules_linkage_correctly(other_module, e, name); - // LLVMSetLinkage(other_g.value, LLVMExternalLinkage); - if (e->Variable.thread_local_model != "") { LLVMSetThreadLocal(g.value, true); @@ -2991,7 +3094,9 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) { return g; } } - GB_PANIC("\n\tError in: %s, missing value '%.*s'\n", token_pos_to_string(e->token.pos), LIT(e->token.string)); + + GB_PANIC("\n\tError in: %s, missing value '%.*s' in module %s\n", + token_pos_to_string(e->token.pos), LIT(e->token.string), m->module_name); return {}; } diff --git a/src/llvm_backend_opt.cpp b/src/llvm_backend_opt.cpp index 6a6d2f802..e6ccc9a57 100644 --- a/src/llvm_backend_opt.cpp +++ b/src/llvm_backend_opt.cpp @@ -398,16 +398,20 @@ gb_internal LLVMValueRef lb_run_instrumentation_pass_insert_call(lbProcedure *p, LLVMValueRef args[3] = {}; args[0] = p->value; - LLVMValueRef returnaddress_args[1] = {}; + if (is_arch_wasm()) { + args[1] = LLVMConstPointerNull(lb_type(m, t_rawptr)); + } else { + LLVMValueRef returnaddress_args[1] = {}; - returnaddress_args[0] = LLVMConstInt(LLVMInt32TypeInContext(m->ctx), 0, false); + returnaddress_args[0] = LLVMConstInt(LLVMInt32TypeInContext(m->ctx), 0, false); - char const *instrinsic_name = "llvm.returnaddress"; - unsigned id = LLVMLookupIntrinsicID(instrinsic_name, gb_strlen(instrinsic_name)); - GB_ASSERT_MSG(id != 0, "Unable to find %s", instrinsic_name); - LLVMValueRef ip = LLVMGetIntrinsicDeclaration(m->mod, id, nullptr, 0); - LLVMTypeRef call_type = LLVMIntrinsicGetType(m->ctx, id, nullptr, 0); - args[1] = LLVMBuildCall2(dummy_builder, call_type, ip, returnaddress_args, gb_count_of(returnaddress_args), ""); + char const *instrinsic_name = "llvm.returnaddress"; + unsigned id = LLVMLookupIntrinsicID(instrinsic_name, gb_strlen(instrinsic_name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s", instrinsic_name); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(m->mod, id, nullptr, 0); + LLVMTypeRef call_type = LLVMIntrinsicGetType(m->ctx, id, nullptr, 0); + args[1] = LLVMBuildCall2(dummy_builder, call_type, ip, returnaddress_args, gb_count_of(returnaddress_args), ""); + } Token name = {}; if (p->entity) { diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index f73698d34..2f736ff6c 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -159,35 +159,41 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i case ProcInlining_no_inline: lb_add_attribute_to_proc(m, p->value, "noinline"); break; + default: + if (build_context.internal_no_inline) { + lb_add_attribute_to_proc(m, p->value, "noinline"); + break; + } } switch (entity->Procedure.optimization_mode) { case ProcedureOptimizationMode_None: - break; - case ProcedureOptimizationMode_Minimal: lb_add_attribute_to_proc(m, p->value, "optnone"); lb_add_attribute_to_proc(m, p->value, "noinline"); break; - case ProcedureOptimizationMode_Size: - lb_add_attribute_to_proc(m, p->value, "optsize"); - break; - case ProcedureOptimizationMode_Speed: - // TODO(bill): handle this correctly + case ProcedureOptimizationMode_FavorSize: lb_add_attribute_to_proc(m, p->value, "optsize"); break; } - if (!entity->Procedure.target_feature_disabled && - entity->Procedure.target_feature.len != 0) { - auto features = split_by_comma(entity->Procedure.target_feature); - for_array(i, features) { - String feature = features[i]; - LLVMAttributeRef ref = LLVMCreateStringAttribute( - m->ctx, - cast(char const *)feature.text, cast(unsigned)feature.len, - "", 0); - LLVMAddAttributeAtIndex(p->value, LLVMAttributeIndex_FunctionIndex, ref); + if (pt->Proc.enable_target_feature.len != 0) { + gbString feature_str = gb_string_make(temporary_allocator(), ""); + + String_Iterator it = {pt->Proc.enable_target_feature, 0}; + bool first = true; + for (;;) { + String str = string_split_iterator(&it, ','); + if (str == "") break; + if (!first) { + feature_str = gb_string_appendc(feature_str, ","); + } + first = false; + + feature_str = gb_string_appendc(feature_str, "+"); + feature_str = gb_string_append_length(feature_str, str.text, str.len); } + + lb_add_attribute_to_proc_with_string(m, p->value, make_string_c("target-features"), make_string_c(feature_str)); } if (entity->flags & EntityFlag_Cold) { @@ -226,7 +232,7 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i if (p->is_foreign) { - lb_set_wasm_import_attributes(p->value, entity, p->name); + lb_set_wasm_procedure_import_attributes(p->value, entity, p->name); } @@ -252,6 +258,11 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i if (e->flags&EntityFlag_NoAlias) { lb_add_proc_attribute_at_index(p, offset+parameter_index, "noalias"); } + if (e->flags&EntityFlag_NoCapture) { + if (is_type_internally_pointer_like(e->type)) { + lb_add_proc_attribute_at_index(p, offset+parameter_index, "nocapture"); + } + } parameter_index += 1; } } @@ -516,6 +527,7 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { lb_start_block(p, p->entry_block); map_init(&p->direct_parameters); + p->variadic_reuses.allocator = heap_allocator(); GB_ASSERT(p->type != nullptr); @@ -703,13 +715,12 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { lb_set_debug_position_to_procedure_begin(p); if (p->debug_info != nullptr) { if (p->context_stack.count != 0) { + lbBlock *prev_block = p->curr_block; p->curr_block = p->decl_block; lb_add_debug_context_variable(p, lb_find_or_generate_context_ptr(p)); + p->curr_block = prev_block; } - } - - lb_start_block(p, p->entry_block); } gb_internal void lb_end_procedure_body(lbProcedure *p) { @@ -1090,15 +1101,7 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c ptr = lb_address_from_load_or_generate_local(p, x); } } else { - if (LLVMIsConstant(x.value)) { - // NOTE(bill): if the value is already constant, then just it as a global variable - // and pass it by pointer - lbAddr addr = lb_add_global_generated(p->module, original_type, x); - lb_make_global_private_const(addr); - ptr = addr.addr; - } else { - ptr = lb_copy_value_to_ptr(p, x, original_type, 16); - } + ptr = lb_copy_value_to_ptr(p, x, original_type, 16); } array_add(&processed_args, ptr); } @@ -2282,6 +2285,39 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu return res; } + case BuiltinProc_add_sat: + case BuiltinProc_sub_sat: + { + Type *main_type = tv.type; + Type *type = main_type; + + lbValue x = lb_build_expr(p, ce->args[0]); + lbValue y = lb_build_expr(p, ce->args[1]); + x = lb_emit_conv(p, x, type); + y = lb_emit_conv(p, y, type); + + char const *name = nullptr; + if (is_type_unsigned(type)) { + switch (id) { + case BuiltinProc_add_sat: name = "llvm.uadd.sat"; break; + case BuiltinProc_sub_sat: name = "llvm.usub.sat"; break; + } + } else { + switch (id) { + case BuiltinProc_add_sat: name = "llvm.sadd.sat"; break; + case BuiltinProc_sub_sat: name = "llvm.ssub.sat"; break; + } + } + LLVMTypeRef types[1] = {lb_type(p->module, type)}; + + LLVMValueRef args[2] = { x.value, y.value }; + + lbValue res = {}; + res.value = lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types)); + res.type = type; + return res; + } + case BuiltinProc_sqrt: { Type *type = tv.type; @@ -2377,9 +2413,10 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu lbValue ptr0 = lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_uintptr); lbValue ptr1 = lb_emit_conv(p, lb_build_expr(p, ce->args[1]), t_uintptr); + ptr0 = lb_emit_conv(p, ptr0, t_int); + ptr1 = lb_emit_conv(p, ptr1, t_int); - lbValue diff = lb_emit_arith(p, Token_Sub, ptr0, ptr1, t_uintptr); - diff = lb_emit_conv(p, diff, t_int); + lbValue diff = lb_emit_arith(p, Token_Sub, ptr0, ptr1, t_int); return lb_emit_arith(p, Token_Quo, diff, lb_const_int(p->module, t_int, type_size_of(elem)), t_int); } @@ -2748,26 +2785,10 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu { GB_ASSERT(arg_count <= 7); - // FreeBSD additionally clobbers r8, r9, r10, but they - // can also be used to pass in arguments, so this needs - // to be handled in two parts. - bool clobber_arg_regs[7] = { - false, false, false, false, false, false, false - }; - if (build_context.metrics.os == TargetOs_freebsd) { - clobber_arg_regs[4] = true; // r10 - clobber_arg_regs[5] = true; // r8 - clobber_arg_regs[6] = true; // r9 - } - char asm_string[] = "syscall"; gbString constraints = gb_string_make(heap_allocator(), "={rax}"); for (unsigned i = 0; i < arg_count; i++) { - if (!clobber_arg_regs[i]) { - constraints = gb_string_appendc(constraints, ",{"); - } else { - constraints = gb_string_appendc(constraints, ",+{"); - } + constraints = gb_string_appendc(constraints, ",{"); static char const *regs[] = { "rax", "rdi", @@ -2791,36 +2812,9 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu // Some but not all system calls will additionally // clobber memory. // - // As a fix for CVE-2019-5595, FreeBSD started - // clobbering R8, R9, and R10, instead of restoring - // them. Additionally unlike Linux, instead of - // returning negative errno, positive errno is - // returned and CF is set. - // // TODO: // * Figure out what Darwin does. - // * Add some extra handling to propagate CF back - // up to the caller on FreeBSD systems so that - // the caller knows that the return value is - // positive errno. constraints = gb_string_appendc(constraints, ",~{rcx},~{r11},~{memory}"); - if (build_context.metrics.os == TargetOs_freebsd) { - // Second half of dealing with FreeBSD's system - // call semantics. Explicitly clobber the registers - // that were not used to pass in arguments, and - // then clobber RFLAGS. - if (arg_count < 5) { - constraints = gb_string_appendc(constraints, ",~{r10}"); - } - if (arg_count < 6) { - constraints = gb_string_appendc(constraints, ",~{r8}"); - } - if (arg_count < 7) { - constraints = gb_string_appendc(constraints, ",~{r9}"); - } - constraints = gb_string_appendc(constraints, ",~{cc}"); - } - inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); } break; @@ -2828,8 +2822,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu { GB_ASSERT(arg_count <= 7); - char asm_string_default[] = "int $$0x80"; - char *asm_string = asm_string_default; + char asm_string[] = "int $$0x80"; gbString constraints = gb_string_make(heap_allocator(), "={eax}"); for (unsigned i = 0; i < gb_min(arg_count, 6); i++) { @@ -2841,16 +2834,11 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu "edx", "esi", "edi", + "ebp", }; constraints = gb_string_appendc(constraints, regs[i]); constraints = gb_string_appendc(constraints, "}"); } - if (arg_count == 7) { - char asm_string7[] = "push %[arg6]\npush %%ebp\nmov 4(%%esp), %%ebp\nint $0x80\npop %%ebp\nadd $4, %%esp"; - asm_string = asm_string7; - - constraints = gb_string_appendc(constraints, ",rm"); - } inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); } @@ -2902,7 +2890,6 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu break; case TargetArch_arm32: { - // TODO(bill): Check this is correct GB_ASSERT(arg_count <= 7); char asm_string[] = "svc #0"; @@ -2910,13 +2897,14 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu for (unsigned i = 0; i < arg_count; i++) { constraints = gb_string_appendc(constraints, ",{"); static char const *regs[] = { - "r8", + "r7", "r0", "r1", "r2", "r3", "r4", "r5", + "r6", }; constraints = gb_string_appendc(constraints, regs[i]); constraints = gb_string_appendc(constraints, "}"); @@ -2934,6 +2922,139 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu res.type = t_uintptr; return res; } + case BuiltinProc_syscall_bsd: + { + // This is a BSD-style syscall where errors are indicated by a high + // Carry Flag and a positive return value, allowing the kernel to + // return any value that fits into a machine word. + // + // This is unlike Linux, where errors are indicated by a negative + // return value, limiting what can be expressed in one result. + unsigned arg_count = cast(unsigned)ce->args.count; + LLVMValueRef *args = gb_alloc_array(permanent_allocator(), LLVMValueRef, arg_count); + for_array(i, ce->args) { + lbValue arg = lb_build_expr(p, ce->args[i]); + arg = lb_emit_conv(p, arg, t_uintptr); + args[i] = arg.value; + } + + LLVMTypeRef llvm_uintptr = lb_type(p->module, t_uintptr); + LLVMTypeRef *llvm_arg_types = gb_alloc_array(permanent_allocator(), LLVMTypeRef, arg_count); + for (unsigned i = 0; i < arg_count; i++) { + llvm_arg_types[i] = llvm_uintptr; + } + + LLVMTypeRef *results = gb_alloc_array(permanent_allocator(), LLVMTypeRef, 2); + results[0] = lb_type(p->module, t_uintptr); + results[1] = lb_type(p->module, t_bool); + LLVMTypeRef llvm_results = LLVMStructTypeInContext(p->module->ctx, results, 2, false); + + LLVMTypeRef func_type = LLVMFunctionType(llvm_results, llvm_arg_types, arg_count, false); + + LLVMValueRef inline_asm = nullptr; + + switch (build_context.metrics.arch) { + case TargetArch_amd64: + { + GB_ASSERT(arg_count <= 7); + + char asm_string[] = "syscall; setnb %cl"; + + // Using CL as an output; RCX doesn't need to get clobbered later. + gbString constraints = gb_string_make(heap_allocator(), "={rax},={cl}"); + for (unsigned i = 0; i < arg_count; i++) { + constraints = gb_string_appendc(constraints, ",{"); + static char const *regs[] = { + "rax", + "rdi", + "rsi", + "rdx", + "r10", + "r8", + "r9", + }; + constraints = gb_string_appendc(constraints, regs[i]); + constraints = gb_string_appendc(constraints, "}"); + } + + // NOTE(Feoramund): If you're experiencing instability + // regarding syscalls during optimized builds, it is + // possible that the ABI has changed for your platform, + // or I've missed a register clobber. + // + // Documentation on this topic is sparse, but I was able to + // determine what registers were being clobbered by adding + // dummy values to them, setting a breakpoint after the + // syscall, and checking the state of the registers afterwards. + // + // Be advised that manually stepping through a debugger may + // cause the kernel to not return via sysret, which will + // preserve register state that normally would've been + // otherwise clobbered. + // + // It is also possible that some syscalls clobber different registers. + + if (build_context.metrics.os == TargetOs_freebsd) { + // As a fix for CVE-2019-5595, FreeBSD started + // clobbering R8, R9, and R10, instead of restoring + // them. + // + // More info here: + // + // https://www.freebsd.org/security/advisories/FreeBSD-SA-19:01.syscall.asc + // https://github.com/freebsd/freebsd-src/blob/098dbd7ff7f3da9dda03802cdb2d8755f816eada/sys/amd64/amd64/exception.S#L605 + // https://stackoverflow.com/q/66878250 + constraints = gb_string_appendc(constraints, ",~{r8},~{r9},~{r10}"); + } + + // Both FreeBSD and NetBSD might clobber RDX. + // + // For NetBSD, it was clobbered during a call to sysctl. + // + // For FreeBSD, it's listed as "return value 2" in their + // AMD64 assembly, so there's no guarantee that it will persist. + constraints = gb_string_appendc(constraints, ",~{rdx},~{r11},~{cc},~{memory}"); + + inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); + } + break; + case TargetArch_arm64: + { + GB_ASSERT(arg_count <= 7); + + char asm_string[] = "svc #0; cset x8, cc"; + gbString constraints = gb_string_make(heap_allocator(), "={x0},={x8}"); + for (unsigned i = 0; i < arg_count; i++) { + constraints = gb_string_appendc(constraints, ",{"); + static char const *regs[] = { + "x8", + "x0", + "x1", + "x2", + "x3", + "x4", + "x5", + }; + constraints = gb_string_appendc(constraints, regs[i]); + constraints = gb_string_appendc(constraints, "}"); + } + + // FreeBSD clobbered x1 on a call to sysctl. + constraints = gb_string_appendc(constraints, ",~{x1},~{cc},~{memory}"); + + inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); + } + break; + default: + GB_PANIC("Unsupported platform"); + } + + lbValue res = {}; + res.value = LLVMBuildCall2(p->builder, func_type, inline_asm, args, arg_count, ""); + res.type = make_optional_ok_type(t_uintptr, true); + + return res; + } case BuiltinProc_objc_send: return lb_handle_objc_send(p, expr); @@ -3373,17 +3494,67 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } isize slice_len = var_args.count; if (slice_len > 0) { - lbAddr slice = lb_add_local_generated(p, slice_type, true); - lbAddr base_array = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true); + lbAddr slice = {}; + + for (auto const &vr : p->variadic_reuses) { + if (are_types_identical(vr.slice_type, slice_type)) { + slice = vr.slice_addr; + break; + } + } + + DeclInfo *d = decl_info_of_entity(p->entity); + if (d != nullptr && slice.addr.value == nullptr) { + for (auto const &vr : d->variadic_reuses) { + if (are_types_identical(vr.slice_type, slice_type)) { + #if LLVM_VERSION_MAJOR >= 13 + // NOTE(bill): No point wasting even more memory, just reuse this stack variable too + if (p->variadic_reuses.count > 0) { + slice = p->variadic_reuses[0].slice_addr; + } else { + slice = lb_add_local_generated(p, slice_type, true); + } + // NOTE(bill): Change the underlying type to match the specific type + slice.addr.type = alloc_type_pointer(slice_type); + #else + slice = lb_add_local_generated(p, slice_type, true); + #endif + array_add(&p->variadic_reuses, lbVariadicReuseSlices{slice_type, slice}); + break; + } + } + } + + lbValue base_array_ptr = p->variadic_reuse_base_array_ptr.addr; + if (base_array_ptr.value == nullptr) { + if (d != nullptr) { + i64 max_bytes = d->variadic_reuse_max_bytes; + i64 max_align = gb_max(d->variadic_reuse_max_align, 16); + p->variadic_reuse_base_array_ptr = lb_add_local_generated(p, alloc_type_array(t_u8, max_bytes), true); + lb_try_update_alignment(p->variadic_reuse_base_array_ptr.addr, cast(unsigned)max_align); + base_array_ptr = p->variadic_reuse_base_array_ptr.addr; + } else { + base_array_ptr = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true).addr; + } + } + + if (slice.addr.value == nullptr) { + slice = lb_add_local_generated(p, slice_type, true); + } + + GB_ASSERT(base_array_ptr.value != nullptr); + GB_ASSERT(slice.addr.value != nullptr); + + base_array_ptr = lb_emit_conv(p, base_array_ptr, alloc_type_pointer(alloc_type_array(elem_type, slice_len))); for (isize i = 0; i < var_args.count; i++) { - lbValue addr = lb_emit_array_epi(p, base_array.addr, cast(i32)i); + lbValue addr = lb_emit_array_epi(p, base_array_ptr, cast(i32)i); lbValue var_arg = var_args[i]; var_arg = lb_emit_conv(p, var_arg, elem_type); lb_emit_store(p, addr, var_arg); } - lbValue base_elem = lb_emit_array_epi(p, base_array.addr, 0); + lbValue base_elem = lb_emit_array_epi(p, base_array_ptr, 0); lbValue len = lb_const_int(p->module, t_int, slice_len); lb_fill_slice(p, slice, base_elem, len); diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 24dd321f6..e70cc503e 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -802,8 +802,19 @@ gb_internal void lb_build_range_enum(lbProcedure *p, Type *enum_type, Type *val_ if (done_) *done_ = done; } -gb_internal void lb_build_range_tuple(lbProcedure *p, Ast *expr, Type *val0_type, Type *val1_type, - lbValue *val0_, lbValue *val1_, lbBlock **loop_, lbBlock **done_) { +gb_internal void lb_build_range_tuple(lbProcedure *p, AstRangeStmt *rs, Scope *scope) { + Ast *expr = unparen_expr(rs->expr); + + Type *expr_type = type_of_expr(expr); + Type *et = base_type(type_deref(expr_type)); + GB_ASSERT(et->kind == Type_Tuple); + + i32 value_count = cast(i32)et->Tuple.variables.count; + + lbValue *values = gb_alloc_array(permanent_allocator(), lbValue, value_count); + + lb_open_scope(p, scope); + lbBlock *loop = lb_create_block(p, "for.tuple.loop"); lb_emit_jump(p, loop); lb_start_block(p, loop); @@ -821,11 +832,26 @@ gb_internal void lb_build_range_tuple(lbProcedure *p, Ast *expr, Type *val0_type lb_emit_if(p, cond, body, done); lb_start_block(p, body); + for (i32 i = 0; i < value_count; i++) { + values[i] = lb_emit_tuple_ev(p, tuple_value, i); + } - if (val0_) *val0_ = lb_emit_tuple_ev(p, tuple_value, 0); - if (val1_) *val1_ = lb_emit_tuple_ev(p, tuple_value, 1); - if (loop_) *loop_ = loop; - if (done_) *done_ = done; + GB_ASSERT(rs->vals.count <= value_count); + for (isize i = 0; i < rs->vals.count; i++) { + Ast *val = rs->vals[i]; + if (val != nullptr) { + lb_store_range_stmt_val(p, val, values[i]); + } + } + + lb_push_target_list(p, rs->label, done, loop, nullptr); + + lb_build_stmt(p, rs->body); + + lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_pop_target_list(p); + lb_emit_jump(p, loop); + lb_start_block(p, done); } gb_internal void lb_build_range_stmt_struct_soa(lbProcedure *p, AstRangeStmt *rs, Scope *scope) { @@ -968,6 +994,17 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc } } + TypeAndValue tav = type_and_value_of_expr(expr); + if (tav.mode != Addressing_Type) { + Type *expr_type = type_of_expr(expr); + Type *et = base_type(type_deref(expr_type)); + if (et->kind == Type_Tuple) { + lb_build_range_tuple(p, rs, scope); + return; + } + } + + lb_open_scope(p, scope); Ast *val0 = rs->vals.count > 0 ? lb_strip_and_prefix(rs->vals[0]) : nullptr; @@ -986,7 +1023,6 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc lbBlock *loop = nullptr; lbBlock *done = nullptr; bool is_map = false; - TypeAndValue tav = type_and_value_of_expr(expr); if (tav.mode == Addressing_Type) { lb_build_range_enum(p, type_deref(tav.type), val0_type, &val, &key, &loop, &done); @@ -1062,8 +1098,7 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc break; } case Type_Tuple: - lb_build_range_tuple(p, expr, val0_type, val1_type, &val, &key, &loop, &done); - break; + GB_PANIC("Should be handled already"); case Type_BitSet: { lbModule *m = p->module; @@ -1544,7 +1579,8 @@ gb_internal void lb_store_type_case_implicit(lbProcedure *p, Ast *clause, lbValu lb_addr_store(p, x, value); } else { if (!is_default_case) { - GB_ASSERT_MSG(are_types_identical(e->type, type_deref(value.type)), "%s %s", type_to_string(e->type), type_to_string(value.type)); + Type *clause_type = e->type; + GB_ASSERT_MSG(are_types_identical(type_deref(clause_type), type_deref(value.type)), "%s %s", type_to_string(clause_type), type_to_string(value.type)); } lb_add_entity(p->module, e, value); } @@ -1700,10 +1736,17 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss for (Ast *clause : body->stmts) { ast_node(cc, CaseClause, clause); + + Entity *case_entity = implicit_entity_of_node(clause); lb_open_scope(p, cc->scope); + if (cc->list.count == 0) { lb_start_block(p, default_block); - lb_store_type_case_implicit(p, clause, parent_value, true); + if (case_entity->flags & EntityFlag_Value) { + lb_store_type_case_implicit(p, clause, parent_value, true); + } else { + lb_store_type_case_implicit(p, clause, parent_ptr, true); + } lb_type_case_body(p, ss->label, clause, p->curr_block, done); continue; } @@ -1733,7 +1776,6 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss LLVMAddCase(switch_instr, on_val.value, body->block); } - Entity *case_entity = implicit_entity_of_node(clause); lb_start_block(p, body); @@ -1746,6 +1788,7 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss } else if (switch_kind == TypeSwitch_Any) { data = lb_emit_load(p, lb_emit_struct_ep(p, parent_ptr, 0)); } + GB_ASSERT(is_type_pointer(data.type)); Type *ct = case_entity->type; Type *ct_ptr = alloc_type_pointer(ct); @@ -1815,7 +1858,9 @@ gb_internal void lb_build_static_variables(lbProcedure *p, AstValueDecl *vd) { LLVMSetInitializer(global, LLVMConstNull(lb_type(p->module, e->type))); if (value.value != nullptr) { LLVMSetInitializer(global, value.value); - } else { + } + if (e->Variable.is_rodata) { + LLVMSetGlobalConstant(global, true); } if (e->Variable.thread_local_model != "") { LLVMSetThreadLocal(global, true); @@ -2122,6 +2167,16 @@ gb_internal void lb_build_if_stmt(lbProcedure *p, Ast *node) { lb_open_scope(p, is->scope); // Scope #1 defer (lb_close_scope(p, lbDeferExit_Default, nullptr)); + lbBlock *then = lb_create_block(p, "if.then"); + lbBlock *done = lb_create_block(p, "if.done"); + lbBlock *else_ = done; + if (is->else_stmt != nullptr) { + else_ = lb_create_block(p, "if.else"); + } + if (is->label != nullptr) { + lbTargetList *tl = lb_push_target_list(p, is->label, done, nullptr, nullptr); + tl->is_block = true; + } if (is->init != nullptr) { lbBlock *init = lb_create_block(p, "if.init"); lb_emit_jump(p, init); @@ -2129,22 +2184,12 @@ gb_internal void lb_build_if_stmt(lbProcedure *p, Ast *node) { lb_build_stmt(p, is->init); } - lbBlock *then = lb_create_block(p, "if.then"); - lbBlock *done = lb_create_block(p, "if.done"); - lbBlock *else_ = done; - if (is->else_stmt != nullptr) { - else_ = lb_create_block(p, "if.else"); - } lbValue cond = lb_build_cond(p, is->cond, then, else_); // Note `cond.value` only set for non-and/or conditions and const negs so that the `LLVMIsConstant()` // and `LLVMConstIntGetZExtValue()` calls below will be valid and `LLVMInstructionEraseFromParent()` // will target the correct (& only) branch statement - if (is->label != nullptr) { - lbTargetList *tl = lb_push_target_list(p, is->label, done, nullptr, nullptr); - tl->is_block = true; - } if (cond.value && LLVMIsConstant(cond.value)) { // NOTE(bill): Do a compile time short circuit for when the condition is constantly known. @@ -2209,15 +2254,6 @@ gb_internal void lb_build_for_stmt(lbProcedure *p, Ast *node) { if (p->debug_info != nullptr) { LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, node)); } - - if (fs->init != nullptr) { - #if 1 - lbBlock *init = lb_create_block(p, "for.init"); - lb_emit_jump(p, init); - lb_start_block(p, init); - #endif - lb_build_stmt(p, fs->init); - } lbBlock *body = lb_create_block(p, "for.body"); lbBlock *done = lb_create_block(p, "for.done"); // NOTE(bill): Append later lbBlock *loop = body; @@ -2229,6 +2265,17 @@ gb_internal void lb_build_for_stmt(lbProcedure *p, Ast *node) { post = lb_create_block(p, "for.post"); } + lb_push_target_list(p, fs->label, done, post, nullptr); + + if (fs->init != nullptr) { + #if 1 + lbBlock *init = lb_create_block(p, "for.init"); + lb_emit_jump(p, init); + lb_start_block(p, init); + #endif + lb_build_stmt(p, fs->init); + } + lb_emit_jump(p, loop); lb_start_block(p, loop); @@ -2241,7 +2288,6 @@ gb_internal void lb_build_for_stmt(lbProcedure *p, Ast *node) { lb_start_block(p, body); } - lb_push_target_list(p, fs->label, done, post, nullptr); lb_build_stmt(p, fs->body); diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index e202a59ba..638170bfc 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -2,7 +2,7 @@ gb_internal isize lb_type_info_index(CheckerInfo *info, Type *type, bool err_on_ auto *set = &info->minimum_dependency_type_info_set; isize index = type_info_index(info, type, err_on_not_found); if (index >= 0) { - auto *found = map_get(set, index); + auto *found = map_get(set, index+1); if (found) { GB_ASSERT(*found >= 0); return *found + 1; @@ -421,7 +421,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ } TokenPos pos = t->Named.type_name->token.pos; - lbValue loc = lb_const_source_code_location_const(m, proc_name, pos); + lbValue loc = lb_const_source_code_location_as_global_ptr(m, proc_name, pos); LLVMValueRef vals[4] = { lb_const_string(m, t->Named.type_name->token.string).value, @@ -810,19 +810,18 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ case Type_Struct: { tag_type = t_type_info_struct; - LLVMValueRef vals[13] = {}; + LLVMValueRef vals[11] = {}; { - lbValue is_packed = lb_const_bool(m, t_bool, t->Struct.is_packed); - lbValue is_raw_union = lb_const_bool(m, t_bool, t->Struct.is_raw_union); - lbValue is_no_copy = lb_const_bool(m, t_bool, t->Struct.is_no_copy); - lbValue is_custom_align = lb_const_bool(m, t_bool, t->Struct.custom_align != 0); - vals[5] = is_packed.value; - vals[6] = is_raw_union.value; - vals[7] = is_no_copy.value; - vals[8] = is_custom_align.value; + u8 flags = 0; + if (t->Struct.is_packed) flags |= 1<<0; + if (t->Struct.is_raw_union) flags |= 1<<1; + if (t->Struct.is_no_copy) flags |= 1<<2; + if (t->Struct.custom_align) flags |= 1<<3; + + vals[6] = lb_const_int(m, t_u8, flags).value; if (is_type_comparable(t) && !is_type_simple_compare(t)) { - vals[9] = lb_equal_proc_for_type(m, t).value; + vals[10] = lb_equal_proc_for_type(m, t).value; } @@ -831,11 +830,11 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ lbValue soa_kind = lb_const_value(m, kind_type, exact_value_i64(t->Struct.soa_kind)); LLVMValueRef soa_type = get_type_info_ptr(m, t->Struct.soa_elem); - lbValue soa_len = lb_const_int(m, t_int, t->Struct.soa_count); + lbValue soa_len = lb_const_int(m, t_i32, t->Struct.soa_count); - vals[10] = soa_kind.value; - vals[11] = soa_type; - vals[12] = soa_len.value; + vals[7] = soa_kind.value; + vals[8] = soa_len.value; + vals[9] = soa_type; } } @@ -882,12 +881,13 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ } - lbValue cv = lb_const_int(m, t_int, count); - vals[0] = llvm_const_slice(m, memory_types, cv); - vals[1] = llvm_const_slice(m, memory_names, cv); - vals[2] = llvm_const_slice(m, memory_offsets, cv); - vals[3] = llvm_const_slice(m, memory_usings, cv); - vals[4] = llvm_const_slice(m, memory_tags, cv); + lbValue cv = lb_const_int(m, t_i32, count); + vals[0] = memory_types.value; + vals[1] = memory_names.value; + vals[2] = memory_offsets.value; + vals[3] = memory_usings.value; + vals[4] = memory_tags.value; + vals[5] = cv.value; } for (isize i = 0; i < gb_count_of(vals); i++) { if (vals[i] == nullptr) { @@ -994,7 +994,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ { tag_type = t_type_info_bit_field; - LLVMValueRef vals[6] = {}; + LLVMValueRef vals[7] = {}; vals[0] = get_type_info_ptr(m, t->BitField.backing_type); isize count = t->BitField.fields.count; if (count > 0) { @@ -1035,11 +1035,12 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ } lbValue cv = lb_const_int(m, t_int, count); - vals[1] = llvm_const_slice(m, memory_names, cv); - vals[2] = llvm_const_slice(m, memory_types, cv); - vals[3] = llvm_const_slice(m, memory_bit_sizes, cv); - vals[4] = llvm_const_slice(m, memory_bit_offsets, cv); - vals[5] = llvm_const_slice(m, memory_tags, cv); + vals[1] = memory_names.value; + vals[2] = memory_types.value; + vals[3] = memory_bit_sizes.value; + vals[4] = memory_bit_offsets.value; + vals[5] = memory_tags.value; + vals[6] = cv.value; } diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 2dd7fbc38..1165476be 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -57,6 +57,29 @@ gb_internal lbValue lb_correct_endianness(lbProcedure *p, lbValue value) { return value; } + +gb_internal void lb_set_metadata_custom_u64(lbModule *m, LLVMValueRef v_ref, String name, u64 value) { + unsigned md_id = LLVMGetMDKindIDInContext(m->ctx, cast(char const *)name.text, cast(unsigned)name.len); + LLVMMetadataRef md = LLVMValueAsMetadata(LLVMConstInt(lb_type(m, t_u64), value, false)); + LLVMValueRef node = LLVMMetadataAsValue(m->ctx, LLVMMDNodeInContext2(m->ctx, &md, 1)); + LLVMSetMetadata(v_ref, md_id, node); +} +gb_internal u64 lb_get_metadata_custom_u64(lbModule *m, LLVMValueRef v_ref, String name) { + unsigned md_id = LLVMGetMDKindIDInContext(m->ctx, cast(char const *)name.text, cast(unsigned)name.len); + LLVMValueRef v_md = LLVMGetMetadata(v_ref, md_id); + if (v_md == nullptr) { + return 0; + } + unsigned node_count = LLVMGetMDNodeNumOperands(v_md); + if (node_count == 0) { + return 0; + } + GB_ASSERT(node_count == 1); + LLVMValueRef value = nullptr; + LLVMGetMDNodeOperands(v_md, &value); + return LLVMConstIntGetZExtValue(value); +} + gb_internal LLVMValueRef lb_mem_zero_ptr_internal(lbProcedure *p, LLVMValueRef ptr, usize len, unsigned alignment, bool is_volatile) { return lb_mem_zero_ptr_internal(p, ptr, LLVMConstInt(lb_type(p->module, t_uint), len, false), alignment, is_volatile); } @@ -97,15 +120,12 @@ gb_internal void lb_mem_zero_ptr(lbProcedure *p, LLVMValueRef ptr, Type *type, u LLVMTypeRef llvm_type = lb_type(p->module, type); LLVMTypeKind kind = LLVMGetTypeKind(llvm_type); - + i64 sz = type_size_of(type); switch (kind) { case LLVMStructTypeKind: case LLVMArrayTypeKind: - { - // NOTE(bill): Enforce zeroing through memset to make sure padding is zeroed too - i32 sz = cast(i32)type_size_of(type); - lb_mem_zero_ptr_internal(p, ptr, lb_const_int(p->module, t_int, sz).value, alignment, false); - } + // NOTE(bill): Enforce zeroing through memset to make sure padding is zeroed too + lb_mem_zero_ptr_internal(p, ptr, lb_const_int(p->module, t_int, sz).value, alignment, false); break; default: LLVMBuildStore(p->builder, LLVMConstNull(lb_type(p->module, type)), ptr); @@ -308,6 +328,7 @@ gb_internal lbValue lb_soa_zip(lbProcedure *p, AstCallExpr *ce, TypeAndValue con lbAddr res = lb_add_local_generated(p, tv.type, true); for_array(i, slices) { lbValue src = lb_slice_elem(p, slices[i]); + src = lb_emit_conv(p, src, alloc_type_pointer_to_multi_pointer(src.type)); lbValue dst = lb_emit_struct_ep(p, res.addr, cast(i32)i); lb_emit_store(p, dst, src); } @@ -1151,7 +1172,15 @@ gb_internal lbValue lb_emit_struct_ep(lbProcedure *p, lbValue s, i32 index) { GB_ASSERT_MSG(result_type != nullptr, "%s %d", type_to_string(t), index); - return lb_emit_struct_ep_internal(p, s, index, result_type); + lbValue gep = lb_emit_struct_ep_internal(p, s, index, result_type); + + Type *bt = base_type(t); + if (bt->kind == Type_Struct && bt->Struct.is_packed) { + lb_set_metadata_custom_u64(p->module, gep.value, ODIN_METADATA_IS_PACKED, 1); + GB_ASSERT(lb_get_metadata_custom_u64(p->module, gep.value, ODIN_METADATA_IS_PACKED) == 1); + } + + return gep; } gb_internal lbValue lb_emit_tuple_ev(lbProcedure *p, lbValue value, i32 index) { @@ -1336,6 +1365,8 @@ gb_internal lbValue lb_emit_deep_field_gep(lbProcedure *p, lbValue e, Selection } else { e = lb_emit_ptr_offset(p, lb_emit_load(p, arr), index); } + e.type = alloc_type_multi_pointer_to_pointer(e.type); + } else if (is_type_quaternion(type)) { e = lb_emit_struct_ep(p, e, index); } else if (is_type_raw_union(type)) { @@ -1531,19 +1562,26 @@ gb_internal lbValue lb_emit_matrix_ev(lbProcedure *p, lbValue s, isize row, isiz return lb_emit_load(p, ptr); } - gb_internal void lb_fill_slice(lbProcedure *p, lbAddr const &slice, lbValue base_elem, lbValue len) { Type *t = lb_addr_type(slice); GB_ASSERT(is_type_slice(t)); lbValue ptr = lb_addr_get_ptr(p, slice); - lb_emit_store(p, lb_emit_struct_ep(p, ptr, 0), base_elem); + lbValue data = lb_emit_struct_ep(p, ptr, 0); + if (are_types_identical(type_deref(base_elem.type, true), type_deref(type_deref(data.type), true))) { + base_elem = lb_emit_conv(p, base_elem, type_deref(data.type)); + } + lb_emit_store(p, data, base_elem); lb_emit_store(p, lb_emit_struct_ep(p, ptr, 1), len); } gb_internal void lb_fill_string(lbProcedure *p, lbAddr const &string, lbValue base_elem, lbValue len) { Type *t = lb_addr_type(string); GB_ASSERT(is_type_string(t)); lbValue ptr = lb_addr_get_ptr(p, string); - lb_emit_store(p, lb_emit_struct_ep(p, ptr, 0), base_elem); + lbValue data = lb_emit_struct_ep(p, ptr, 0); + if (are_types_identical(type_deref(base_elem.type, true), type_deref(type_deref(data.type), true))) { + base_elem = lb_emit_conv(p, base_elem, type_deref(data.type)); + } + lb_emit_store(p, data, base_elem); lb_emit_store(p, lb_emit_struct_ep(p, ptr, 1), len); } @@ -1711,7 +1749,8 @@ gb_internal lbValue lb_emit_mul_add(lbProcedure *p, lbValue a, lbValue b, lbValu if (is_possible) { switch (build_context.metrics.arch) { case TargetArch_amd64: - if (type_size_of(t) == 2) { + // NOTE: using the intrinsic when not supported causes slow codegen (See #2928). + if (type_size_of(t) == 2 || !check_target_feature_is_enabled(str_lit("fma"), nullptr)) { is_possible = false; } break; @@ -1979,7 +2018,7 @@ gb_internal LLVMValueRef llvm_get_inline_asm(LLVMTypeRef func_type, String const } -gb_internal void lb_set_wasm_import_attributes(LLVMValueRef value, Entity *entity, String import_name) { +gb_internal void lb_set_wasm_procedure_import_attributes(LLVMValueRef value, Entity *entity, String import_name) { if (!is_arch_wasm()) { return; } @@ -1990,7 +2029,11 @@ gb_internal void lb_set_wasm_import_attributes(LLVMValueRef value, Entity *entit GB_ASSERT(foreign_library->LibraryName.paths.count == 1); module_name = foreign_library->LibraryName.paths[0]; - + + if (string_ends_with(module_name, str_lit(".o"))) { + return; + } + if (string_starts_with(import_name, module_name)) { import_name = substring(import_name, module_name.len+WASM_MODULE_NAME_SEPARATOR.len, import_name.len); } @@ -2186,8 +2229,8 @@ gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(ExactValue const & GB_ASSERT(value.kind == ExactValue_Integer); i64 v = exact_value_to_i64(value); switch (v) { - case OdinAtomicMemoryOrder_relaxed: return LLVMAtomicOrderingUnordered; - case OdinAtomicMemoryOrder_consume: return LLVMAtomicOrderingMonotonic; + case OdinAtomicMemoryOrder_relaxed: return LLVMAtomicOrderingMonotonic; + case OdinAtomicMemoryOrder_consume: return LLVMAtomicOrderingAcquire; case OdinAtomicMemoryOrder_acquire: return LLVMAtomicOrderingAcquire; case OdinAtomicMemoryOrder_release: return LLVMAtomicOrderingRelease; case OdinAtomicMemoryOrder_acq_rel: return LLVMAtomicOrderingAcquireRelease; diff --git a/src/main.cpp b/src/main.cpp index ee7de7f81..00734c050 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -61,6 +61,7 @@ gb_global Timings global_timings = {0}; #include "llvm-c/Types.h" #else #include +#include #endif #include "parser.hpp" @@ -70,6 +71,8 @@ gb_global Timings global_timings = {0}; #include "checker.cpp" #include "docs.cpp" +#include "cached.cpp" + #include "linker.cpp" #if defined(GB_SYSTEM_WINDOWS) && defined(ODIN_TILDE_BACKEND) @@ -86,27 +89,46 @@ gb_global Timings global_timings = {0}; #if defined(GB_SYSTEM_OSX) #include - #if LLVM_VERSION_MAJOR < 11 - #error LLVM Version 11+ is required => "brew install llvm@11" - #endif - #if (LLVM_VERSION_MAJOR > 14 && LLVM_VERSION_MAJOR < 17) || LLVM_VERSION_MAJOR > 17 - #error LLVM Version 11..=14 or =17 is required => "brew install llvm@14" + #if LLVM_VERSION_MAJOR < 11 || (LLVM_VERSION_MAJOR > 14 && LLVM_VERSION_MAJOR < 17) || LLVM_VERSION_MAJOR > 18 + #error LLVM Version 11..=14 or =18 is required => "brew install llvm@14" #endif #endif #include "bug_report.cpp" // NOTE(bill): 'name' is used in debugging and profiling modes -gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, ...) { +gb_internal i32 system_exec_command_line_app_internal(bool exit_on_err, char const *name, char const *fmt, va_list va) { isize const cmd_cap = 64<<20; // 64 MiB should be more than enough char *cmd_line = gb_alloc_array(gb_heap_allocator(), char, cmd_cap); isize cmd_len = 0; - va_list va; i32 exit_code = 0; - va_start(va, fmt); cmd_len = gb_snprintf_va(cmd_line, cmd_cap-1, fmt, va); - va_end(va); + + if (build_context.print_linker_flags) { + // NOTE(bill): remove the first argument (the executable) from the executable list + // and then print it for the "linker flags" + while (*cmd_line && gb_char_is_space(*cmd_line)) { + cmd_line++; + } + if (*cmd_line == '\"') for (cmd_line++; *cmd_line; cmd_line++) { + if (*cmd_line == '\\') { + cmd_line++; + if (*cmd_line == '\"') { + cmd_line++; + } + } else if (*cmd_line == '\"') { + cmd_line++; + break; + } + } + while (*cmd_line && gb_char_is_space(*cmd_line)) { + cmd_line++; + } + + fprintf(stdout, "%s\n", cmd_line); + return exit_code; + } #if defined(GB_SYSTEM_WINDOWS) STARTUPINFOW start_info = {gb_size_of(STARTUPINFOW)}; @@ -146,18 +168,68 @@ gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, gb_printf_err("%s\n\n", cmd_line); } exit_code = system(cmd_line); + if (exit_on_err && WIFSIGNALED(exit_code)) { + raise(WTERMSIG(exit_code)); + } if (WIFEXITED(exit_code)) { exit_code = WEXITSTATUS(exit_code); } #endif - if (exit_code) { + if (exit_on_err && exit_code) { exit(exit_code); } return exit_code; } +gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, ...) { + va_list va; + va_start(va, fmt); + i32 exit_code = system_exec_command_line_app_internal(/* exit_on_err= */ false, name, fmt, va); + va_end(va); + return exit_code; +} + +gb_internal void system_must_exec_command_line_app(char const *name, char const *fmt, ...) { + va_list va; + va_start(va, fmt); + system_exec_command_line_app_internal(/* exit_on_err= */ true, name, fmt, va); + va_end(va); +} + +#if defined(GB_SYSTEM_WINDOWS) +#define popen _popen +#define pclose _pclose +#endif + +gb_internal bool system_exec_command_line_app_output(char const *command, gbString *output) { + GB_ASSERT(output); + + u8 buffer[256]; + FILE *stream; + stream = popen(command, "r"); + if (!stream) { + return false; + } + defer (pclose(stream)); + + while (!feof(stream)) { + size_t n = fread(buffer, 1, 255, stream); + *output = gb_string_append_length(*output, buffer, n); + + if (ferror(stream)) { + return false; + } + } + + if (build_context.show_system_calls) { + gb_printf_err("[SYSTEM CALL OUTPUT] %s -> %s\n", command, *output); + } + + return true; +} + gb_internal Array setup_args(int argc, char const **argv) { gbAllocator a = heap_allocator(); @@ -237,6 +309,8 @@ enum BuildFlagKind { BuildFlag_ShowMoreTimings, BuildFlag_ExportTimings, BuildFlag_ExportTimingsFile, + BuildFlag_ExportDependencies, + BuildFlag_ExportDependenciesFile, BuildFlag_ShowSystemCalls, BuildFlag_ThreadCount, BuildFlag_KeepTempFiles, @@ -257,6 +331,9 @@ enum BuildFlagKind { BuildFlag_NoThreadedChecker, BuildFlag_ShowDebugMessages, + BuildFlag_ShowDefineables, + BuildFlag_ExportDefineables, + BuildFlag_Vet, BuildFlag_VetShadowing, BuildFlag_VetUnused, @@ -266,20 +343,22 @@ enum BuildFlagKind { BuildFlag_VetUsingParam, BuildFlag_VetStyle, BuildFlag_VetSemicolon, + BuildFlag_VetCast, + BuildFlag_VetTabs, + BuildFlag_CustomAttribute, BuildFlag_IgnoreUnknownAttributes, BuildFlag_ExtraLinkerFlags, BuildFlag_ExtraAssemblerFlags, BuildFlag_Microarch, BuildFlag_TargetFeatures, + BuildFlag_StrictTargetFeatures, BuildFlag_MinimumOSVersion, BuildFlag_NoThreadLocal, BuildFlag_RelocMode, BuildFlag_DisableRedZone, - BuildFlag_TestName, - BuildFlag_DisallowDo, BuildFlag_DefaultToNilAllocator, BuildFlag_DefaultToPanicAllocator, @@ -307,9 +386,15 @@ enum BuildFlagKind { BuildFlag_MinLinkLibs, + BuildFlag_PrintLinkerFlags, + // internal use only BuildFlag_InternalIgnoreLazy, BuildFlag_InternalIgnoreLLVMBuild, + BuildFlag_InternalIgnorePanic, + BuildFlag_InternalModulePerFile, + BuildFlag_InternalCached, + BuildFlag_InternalNoInline, BuildFlag_Tilde, @@ -322,7 +407,6 @@ enum BuildFlagKind { BuildFlag_Subsystem, #endif - BuildFlag_COUNT, }; @@ -429,6 +513,8 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_ShowMoreTimings, str_lit("show-more-timings"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ExportTimings, str_lit("export-timings"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_ExportTimingsFile, str_lit("export-timings-file"), BuildFlagParam_String, Command__does_check); + add_flag(&build_flags, BuildFlag_ExportDependencies, str_lit("export-dependencies"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_ExportDependenciesFile, str_lit("export-dependencies-file"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_ShowUnused, str_lit("show-unused"), BuildFlagParam_None, Command_check); add_flag(&build_flags, BuildFlag_ShowUnusedWithLocation, str_lit("show-unused-with-location"), BuildFlagParam_None, Command_check); add_flag(&build_flags, BuildFlag_ShowSystemCalls, str_lit("show-system-calls"), BuildFlagParam_None, Command_all); @@ -452,6 +538,9 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_NoThreadedChecker, str_lit("no-threaded-checker"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ShowDebugMessages, str_lit("show-debug-messages"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_ShowDefineables, str_lit("show-defineables"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_ExportDefineables, str_lit("export-defineables"), BuildFlagParam_String, Command__does_check); + add_flag(&build_flags, BuildFlag_Vet, str_lit("vet"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetUnused, str_lit("vet-unused"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetUnusedVariables, str_lit("vet-unused-variables"), BuildFlagParam_None, Command__does_check); @@ -461,19 +550,21 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_VetUsingParam, str_lit("vet-using-param"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetStyle, str_lit("vet-style"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetSemicolon, str_lit("vet-semicolon"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_VetCast, str_lit("vet-cast"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_VetTabs, str_lit("vet-tabs"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_CustomAttribute, str_lit("custom-attribute"), BuildFlagParam_String, Command__does_check, true); add_flag(&build_flags, BuildFlag_IgnoreUnknownAttributes, str_lit("ignore-unknown-attributes"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ExtraLinkerFlags, str_lit("extra-linker-flags"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_ExtraAssemblerFlags, str_lit("extra-assembler-flags"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_Microarch, str_lit("microarch"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_TargetFeatures, str_lit("target-features"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_StrictTargetFeatures, str_lit("strict-target-features"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_MinimumOSVersion, str_lit("minimum-os-version"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_RelocMode, str_lit("reloc-mode"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_DisableRedZone, str_lit("disable-red-zone"), BuildFlagParam_None, Command__does_build); - add_flag(&build_flags, BuildFlag_TestName, str_lit("test-name"), BuildFlagParam_String, Command_test); - add_flag(&build_flags, BuildFlag_DisallowDo, str_lit("disallow-do"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_DefaultToNilAllocator, str_lit("default-to-nil-allocator"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_DefaultToPanicAllocator, str_lit("default-to-panic-allocator"),BuildFlagParam_None, Command__does_check); @@ -501,8 +592,14 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_MinLinkLibs, str_lit("min-link-libs"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_PrintLinkerFlags, str_lit("print-linker-flags"), BuildFlagParam_None, Command_build); + add_flag(&build_flags, BuildFlag_InternalIgnoreLazy, str_lit("internal-ignore-lazy"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalIgnoreLLVMBuild, str_lit("internal-ignore-llvm-build"),BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_InternalIgnorePanic, str_lit("internal-ignore-panic"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_InternalModulePerFile, str_lit("internal-module-per-file"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_InternalCached, str_lit("internal-cached"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_InternalNoInline, str_lit("internal-no-inline"), BuildFlagParam_None, Command_all); #if ALLOW_TILDE add_flag(&build_flags, BuildFlag_Tilde, str_lit("tilde"), BuildFlagParam_None, Command__does_build); @@ -510,6 +607,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Sanitize, str_lit("sanitize"), BuildFlagParam_String, Command__does_build, true); + #if defined(GB_SYSTEM_WINDOWS) add_flag(&build_flags, BuildFlag_IgnoreVsSearch, str_lit("ignore-vs-search"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_ResourceFile, str_lit("resource"), BuildFlagParam_String, Command__does_build); @@ -754,6 +852,53 @@ gb_internal bool parse_build_flags(Array args) { break; } + case BuildFlag_ExportDependencies: { + GB_ASSERT(value.kind == ExactValue_String); + + if (value.value_string == "make") { + build_context.export_dependencies_format = DependenciesExportMake; + } else if (value.value_string == "json") { + build_context.export_dependencies_format = DependenciesExportJson; + } else { + gb_printf_err("Invalid export format for -export-dependencies:, got %.*s\n", LIT(value.value_string)); + gb_printf_err("Valid export formats:\n"); + gb_printf_err("\tmake\n"); + gb_printf_err("\tjson\n"); + bad_flags = true; + } + break; + } + case BuildFlag_ExportDependenciesFile: { + GB_ASSERT(value.kind == ExactValue_String); + + String export_path = string_trim_whitespace(value.value_string); + if (is_build_flag_path_valid(export_path)) { + build_context.export_dependencies_file = path_to_full_path(heap_allocator(), export_path); + } else { + gb_printf_err("Invalid -export-dependencies path, got %.*s\n", LIT(export_path)); + bad_flags = true; + } + + break; + } + case BuildFlag_ShowDefineables: { + GB_ASSERT(value.kind == ExactValue_Invalid); + build_context.show_defineables = true; + break; + } + case BuildFlag_ExportDefineables: { + GB_ASSERT(value.kind == ExactValue_String); + + String export_path = string_trim_whitespace(value.value_string); + if (is_build_flag_path_valid(export_path)) { + build_context.export_defineables_file = path_to_full_path(heap_allocator(), export_path); + } else { + gb_printf_err("Invalid -export-defineables path, got %.*s\n", LIT(export_path)); + bad_flags = true; + } + + break; + } case BuildFlag_ShowSystemCalls: { GB_ASSERT(value.kind == ExactValue_Invalid); build_context.show_system_calls = true; @@ -990,20 +1135,27 @@ gb_internal bool parse_build_flags(Array args) { build_context.build_mode = BuildMode_DynamicLibrary; } else if (str == "obj" || str == "object") { build_context.build_mode = BuildMode_Object; + } else if (str == "static" || str == "lib") { + build_context.build_mode = BuildMode_StaticLibrary; } else if (str == "exe") { build_context.build_mode = BuildMode_Executable; } else if (str == "asm" || str == "assembly" || str == "assembler") { build_context.build_mode = BuildMode_Assembly; } else if (str == "llvm" || str == "llvm-ir") { build_context.build_mode = BuildMode_LLVM_IR; + } else if (str == "test") { + build_context.build_mode = BuildMode_Executable; + build_context.command_kind = Command_test; } else { gb_printf_err("Unknown build mode '%.*s'\n", LIT(str)); gb_printf_err("Valid build modes:\n"); gb_printf_err("\tdll, shared, dynamic\n"); + gb_printf_err("\tlib, static\n"); gb_printf_err("\tobj, object\n"); gb_printf_err("\texe\n"); gb_printf_err("\tasm, assembly, assembler\n"); gb_printf_err("\tllvm, llvm-ir\n"); + gb_printf_err("\ttest\n"); bad_flags = true; break; } @@ -1059,6 +1211,31 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_VetUsingParam: build_context.vet_flags |= VetFlag_UsingParam; break; case BuildFlag_VetStyle: build_context.vet_flags |= VetFlag_Style; break; case BuildFlag_VetSemicolon: build_context.vet_flags |= VetFlag_Semicolon; break; + case BuildFlag_VetCast: build_context.vet_flags |= VetFlag_Cast; break; + case BuildFlag_VetTabs: build_context.vet_flags |= VetFlag_Tabs; break; + + case BuildFlag_CustomAttribute: + { + GB_ASSERT(value.kind == ExactValue_String); + String val = value.value_string; + String_Iterator it = {val, 0}; + for (;;) { + String attr = string_split_iterator(&it, ','); + if (attr.len == 0) { + break; + } + + attr = string_trim_whitespace(attr); + if (!string_is_valid_identifier(attr)) { + gb_printf_err("-custom-attribute '%.*s' must be a valid identifier\n", LIT(attr)); + bad_flags = true; + continue; + } + + string_set_add(&build_context.custom_attributes, attr); + } + } + break; case BuildFlag_IgnoreUnknownAttributes: build_context.ignore_unknown_attributes = true; @@ -1083,6 +1260,9 @@ gb_internal bool parse_build_flags(Array args) { string_to_lower(&build_context.target_features_string); break; } + case BuildFlag_StrictTargetFeatures: + build_context.strict_target_features = true; + break; case BuildFlag_MinimumOSVersion: { GB_ASSERT(value.kind == ExactValue_String); build_context.minimum_os_version_string = value.value_string; @@ -1114,21 +1294,6 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_DisableRedZone: build_context.disable_red_zone = true; break; - case BuildFlag_TestName: { - GB_ASSERT(value.kind == ExactValue_String); - { - String name = value.value_string; - if (!string_is_valid_identifier(name)) { - gb_printf_err("Test name '%.*s' must be a valid identifier\n", LIT(name)); - bad_flags = true; - break; - } - string_set_add(&build_context.test_names, name); - - // NOTE(bill): Allow for multiple -test-name - continue; - } - } case BuildFlag_DisallowDo: build_context.disallow_do = true; break; @@ -1239,12 +1404,31 @@ gb_internal bool parse_build_flags(Array args) { build_context.min_link_libs = true; break; + case BuildFlag_PrintLinkerFlags: + build_context.print_linker_flags = true; + break; + case BuildFlag_InternalIgnoreLazy: build_context.ignore_lazy = true; break; case BuildFlag_InternalIgnoreLLVMBuild: build_context.ignore_llvm_build = true; break; + case BuildFlag_InternalIgnorePanic: + build_context.ignore_panic = true; + break; + case BuildFlag_InternalModulePerFile: + build_context.module_per_file = true; + build_context.use_separate_modules = true; + break; + case BuildFlag_InternalCached: + build_context.cached = true; + build_context.use_separate_modules = true; + break; + case BuildFlag_InternalNoInline: + build_context.internal_no_inline = true; + break; + case BuildFlag_Tilde: build_context.tilde_backend = true; break; @@ -1264,6 +1448,7 @@ gb_internal bool parse_build_flags(Array args) { } break; + #if defined(GB_SYSTEM_WINDOWS) case BuildFlag_IgnoreVsSearch: { GB_ASSERT(value.kind == ExactValue_Invalid); @@ -1275,8 +1460,9 @@ gb_internal bool parse_build_flags(Array args) { String path = value.value_string; path = string_trim_whitespace(path); if (is_build_flag_path_valid(path)) { - if(!string_ends_with(path, str_lit(".rc"))) { - gb_printf_err("Invalid -resource path %.*s, missing .rc\n", LIT(path)); + bool is_resource = string_ends_with(path, str_lit(".rc")) || string_ends_with(path, str_lit(".res")); + if(!is_resource) { + gb_printf_err("Invalid -resource path %.*s, missing .rc or .res file\n", LIT(path)); bad_flags = true; break; } else if (!gb_file_exists((const char *)path.text)) { @@ -1404,6 +1590,16 @@ gb_internal bool parse_build_flags(Array args) { gb_printf_err("`-export-timings:` requires `-show-timings` or `-show-more-timings` to be present\n"); bad_flags = true; } + + + if (build_context.export_dependencies_format != DependenciesExportUnspecified && build_context.print_linker_flags) { + gb_printf_err("-export-dependencies cannot be used with -print-linker-flags\n"); + bad_flags = true; + } else if (build_context.show_timings && build_context.print_linker_flags) { + gb_printf_err("-show-timings/-show-more-timings cannot be used with -print-linker-flags\n"); + bad_flags = true; + } + return !bad_flags; } @@ -1502,6 +1698,115 @@ gb_internal void timings_export_all(Timings *t, Checker *c, bool timings_are_fin gb_printf("Done.\n"); } +gb_internal void check_defines(BuildContext *bc, Checker *c) { + for (auto const &entry : bc->defined_values) { + String name = make_string_c(entry.key); + ExactValue value = entry.value; + GB_ASSERT(value.kind != ExactValue_Invalid); + + bool found = false; + for_array(i, c->info.defineables) { + Defineable *def = &c->info.defineables[i]; + if (def->name == name) { + found = true; + break; + } + } + + if (!found) { + ERROR_BLOCK(); + warning(nullptr, "given -define:%.*s is unused in the project", LIT(name)); + error_line("\tSuggestion: use the -show-defineables flag for an overview of the possible defines\n"); + } + } +} + +gb_internal void temp_alloc_defineable_strings(Checker *c) { + for_array(i, c->info.defineables) { + Defineable *def = &c->info.defineables[i]; + def->default_value_str = make_string_c(write_exact_value_to_string(gb_string_make(temporary_allocator(), ""), def->default_value)); + def->pos_str = make_string_c(token_pos_to_string(def->pos)); + } +} + +gb_internal GB_COMPARE_PROC(defineables_cmp) { + Defineable *x = (Defineable *)a; + Defineable *y = (Defineable *)b; + + int cmp = 0; + + String x_file = get_file_path_string(x->pos.file_id); + String y_file = get_file_path_string(y->pos.file_id); + cmp = string_compare(x_file, y_file); + if (cmp) { + return cmp; + } + + return i32_cmp(x->pos.offset, y->pos.offset); +} + +gb_internal void sort_defineables(Checker *c) { + gb_sort_array(c->info.defineables.data, c->info.defineables.count, defineables_cmp); +} + +gb_internal void export_defineables(Checker *c, String path) { + gbFile f = {}; + gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, (char *)path.text); + if (err != gbFileError_None) { + gb_printf_err("Failed to export defineables to: %.*s\n", LIT(path)); + gb_exit(1); + return; + } else { + gb_printf("Exporting defineables to '%.*s'...\n", LIT(path)); + } + defer (gb_file_close(&f)); + + gbString docs = gb_string_make(heap_allocator(), ""); + defer (gb_string_free(docs)); + + gb_fprintf(&f, "Defineable,Default Value,Docs,Location\n"); + for_array(i, c->info.defineables) { + Defineable *def = &c->info.defineables[i]; + + gb_string_clear(docs); + if (def->docs) { + docs = gb_string_appendc(docs, "\""); + for (Token const &token : def->docs->list) { + for (isize i = 0; i < token.string.len; i++) { + u8 c = token.string.text[i]; + if (c == '"') { + docs = gb_string_appendc(docs, "\"\""); + } else { + docs = gb_string_append_length(docs, &c, 1); + } + } + } + docs = gb_string_appendc(docs, "\""); + } + + gb_fprintf(&f,"%.*s,%.*s,%s,%.*s\n", LIT(def->name), LIT(def->default_value_str), docs, LIT(def->pos_str)); + } +} + +gb_internal void show_defineables(Checker *c) { + for_array(i, c->info.defineables) { + Defineable *def = &c->info.defineables[i]; + if (has_ansi_terminal_colours()) { + gb_printf("\x1b[0;90m"); + } + printf("%.*s\n", LIT(def->pos_str)); + if (def->docs) { + for (Token const &token : def->docs->list) { + gb_printf("%.*s\n", LIT(token.string)); + } + } + if (has_ansi_terminal_colours()) { + gb_printf("\x1b[0m"); + } + gb_printf("%.*s :: %.*s\n\n", LIT(def->name), LIT(def->default_value_str)); + } +} + gb_internal void show_timings(Checker *c, Timings *t) { Parser *p = c->parser; isize lines = p->total_line_count; @@ -1630,11 +1935,123 @@ gb_internal void show_timings(Checker *c, Timings *t) { } } +gb_internal GB_COMPARE_PROC(file_path_cmp) { + AstFile *x = *(AstFile **)a; + AstFile *y = *(AstFile **)b; + return string_compare(x->fullpath, y->fullpath); +} + +gb_internal void export_dependencies(Checker *c) { + GB_ASSERT(build_context.export_dependencies_format != DependenciesExportUnspecified); + + if (build_context.export_dependencies_file.len <= 0) { + gb_printf_err("No dependency file specified with `-export-dependencies-file`\n"); + exit_with_errors(); + return; + } + + Parser *p = c->parser; + + gbFile f = {}; + char * fileName = (char *)build_context.export_dependencies_file.text; + gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, fileName); + if (err != gbFileError_None) { + gb_printf_err("Failed to export dependencies to: %s\n", fileName); + exit_with_errors(); + return; + } + defer (gb_file_close(&f)); + + + auto files = array_make(heap_allocator()); + for (AstPackage *pkg : p->packages) { + for (AstFile *f : pkg->files) { + array_add(&files, f); + } + } + array_sort(files, file_path_cmp); + + + auto load_files = array_make(heap_allocator()); + for (auto const &entry : c->info.load_file_cache) { + auto *cache = entry.value; + if (!cache || !cache->exists) { + continue; + } + array_add(&load_files, cache); + } + array_sort(files, file_cache_sort_cmp); + + if (build_context.export_dependencies_format == DependenciesExportMake) { + String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); + defer (gb_free(heap_allocator(), exe_name.text)); + + gb_fprintf(&f, "%.*s:", LIT(exe_name)); + + isize current_line_length = exe_name.len + 1; + + for_array(i, files) { + AstFile *file = files[i]; + /* Arbitrary line break value. Maybe make this better? */ + if (current_line_length >= 80-2) { + gb_file_write(&f, " \\\n ", 4); + current_line_length = 1; + } + + gb_file_write(&f, " ", 1); + current_line_length++; + + for (isize k = 0; k < file->fullpath.len; k++) { + char part = file->fullpath.text[k]; + if (part == ' ') { + gb_file_write(&f, "\\", 1); + current_line_length++; + } + gb_file_write(&f, &part, 1); + current_line_length++; + } + } + + gb_fprintf(&f, "\n"); + } else if (build_context.export_dependencies_format == DependenciesExportJson) { + gb_fprintf(&f, "{\n"); + + gb_fprintf(&f, "\t\"source_files\": [\n"); + + for_array(i, files) { + AstFile *file = files[i]; + gb_fprintf(&f, "\t\t\"%.*s\"", LIT(file->fullpath)); + if (i+1 == files.count) { + gb_fprintf(&f, ","); + } + gb_fprintf(&f, "\n"); + } + + gb_fprintf(&f, "\t],\n"); + + gb_fprintf(&f, "\t\"load_files\": [\n"); + + for_array(i, load_files) { + LoadFileCache *cache = load_files[i]; + gb_fprintf(&f, "\t\t\"%.*s\"", LIT(cache->path)); + if (i+1 == load_files.count) { + gb_fprintf(&f, ","); + } + gb_fprintf(&f, "\n"); + } + + gb_fprintf(&f, "\t]\n"); + + gb_fprintf(&f, "}\n"); + } +} + gb_internal void remove_temp_files(lbGenerator *gen) { if (build_context.keep_temp_files) return; switch (build_context.build_mode) { case BuildMode_Executable: + case BuildMode_StaticLibrary: case BuildMode_DynamicLibrary: break; @@ -1653,6 +2070,7 @@ gb_internal void remove_temp_files(lbGenerator *gen) { if (!build_context.keep_object_files) { switch (build_context.build_mode) { case BuildMode_Executable: + case BuildMode_StaticLibrary: case BuildMode_DynamicLibrary: for (String const &path : gen->output_object_paths) { gb_file_remove(cast(char const *)path.text); @@ -1754,7 +2172,7 @@ gb_internal void print_show_help(String const arg0, String const &command) { if (LB_USE_NEW_PASS_SYSTEM) { print_usage_line(3, "-o:aggressive"); } - print_usage_line(2, "The default is -o:minimal."); + print_usage_line(2, "The default is -o:none."); print_usage_line(0, ""); } @@ -1783,6 +2201,18 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "Example: -export-timings-file:timings.json"); print_usage_line(0, ""); + print_usage_line(1, "-export-dependencies:"); + print_usage_line(2, "Exports dependencies to one of a few formats. Requires `-export-dependencies-file`."); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-export-dependencies:make Exports in Makefile format"); + print_usage_line(3, "-export-dependencies:json Exports in JSON format"); + print_usage_line(0, ""); + + print_usage_line(1, "-export-dependencies-file:"); + print_usage_line(2, "Specifies the filename for `-export-dependencies`."); + print_usage_line(2, "Example: -export-dependencies-file:dependencies.d"); + print_usage_line(0, ""); + print_usage_line(1, "-thread-count:"); print_usage_line(2, "Overrides the number of threads the compiler will use to compile with."); print_usage_line(2, "Example: -thread-count:2"); @@ -1822,6 +2252,15 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "Usage in code:"); print_usage_line(3, "#config(SPAM, default_value)"); print_usage_line(0, ""); + + print_usage_line(1, "-show-defineables"); + print_usage_line(2, "Shows an overview of all the #config/#defined usages in the project."); + print_usage_line(0, ""); + + print_usage_line(1, "-export-defineables:"); + print_usage_line(2, "Exports an overview of all the #config/#defined usages in CSV format to the given file path."); + print_usage_line(2, "Example: -export-defineables:defineables.csv"); + print_usage_line(0, ""); } if (build) { @@ -1831,6 +2270,8 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(3, "-build-mode:exe Builds as an executable."); 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."); + print_usage_line(3, "-build-mode:static Builds as a statically linked library."); print_usage_line(3, "-build-mode:obj Builds as an object file."); print_usage_line(3, "-build-mode:object Builds as an object file."); print_usage_line(3, "-build-mode:assembly Builds as an assembly file."); @@ -1877,9 +2318,9 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(0, ""); print_usage_line(1, "-use-separate-modules"); - print_usage_line(1, "[EXPERIMENTAL]"); print_usage_line(2, "The backend generates multiple build units which are then linked together."); print_usage_line(2, "Normally, a single build unit is generated for a standard project."); + print_usage_line(2, "This is the default behaviour on Windows for '-o:none' and '-o:minimal' builds."); print_usage_line(0, ""); } @@ -1936,9 +2377,26 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(1, "-vet-semicolon"); print_usage_line(2, "Errs on unneeded semicolons."); print_usage_line(0, ""); + + print_usage_line(1, "-vet-cast"); + print_usage_line(2, "Errs on casting a value to its own type or using `transmute` rather than `cast`."); + print_usage_line(0, ""); + + print_usage_line(1, "-vet-tabs"); + print_usage_line(2, "Errs when the use of tabs has not been used for indentation."); + print_usage_line(0, ""); } if (check) { + print_usage_line(1, "-custom-attribute:"); + print_usage_line(2, "Add a custom attribute which will be ignored if it is unknown."); + print_usage_line(2, "This can be used with metaprogramming tools."); + print_usage_line(2, "Examples:"); + print_usage_line(3, "-custom-attribute:my_tag"); + print_usage_line(3, "-custom-attribute:my_tag,the_other_thing"); + print_usage_line(3, "-custom-attribute:my_tag -custom-attribute:the_other_thing"); + print_usage_line(0, ""); + print_usage_line(1, "-ignore-unknown-attributes"); print_usage_line(2, "Ignores unknown attributes."); print_usage_line(2, "This can be used with metaprogramming tools."); @@ -1952,10 +2410,6 @@ gb_internal void print_show_help(String const arg0, String const &command) { } if (test_only) { - print_usage_line(1, "-test-name:"); - print_usage_line(2, "Runs specific test only by name."); - print_usage_line(0, ""); - print_usage_line(1, "-all-packages"); print_usage_line(2, "Tests all packages imported into the given initial package."); print_usage_line(0, ""); @@ -1981,7 +2435,20 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "Examples:"); print_usage_line(3, "-microarch:sandybridge"); print_usage_line(3, "-microarch:native"); - print_usage_line(3, "-microarch:? for a list"); + print_usage_line(3, "-microarch:\"?\" for a list"); + print_usage_line(0, ""); + + print_usage_line(1, "-target-features:"); + print_usage_line(2, "Specifies CPU features to enable on top of the enabled features implied by -microarch."); + print_usage_line(2, "Examples:"); + print_usage_line(3, "-target-features:atomics"); + print_usage_line(3, "-target-features:\"sse2,aes\""); + print_usage_line(3, "-target-features:\"?\" for a list"); + print_usage_line(0, ""); + + print_usage_line(1, "-strict-target-features"); + print_usage_line(2, "Makes @(enable_target_features=\"...\") behave the same way as @(require_target_features=\"...\")."); + print_usage_line(2, "This enforces that all generated code uses features supported by the combination of -target, -microarch, and -target-features."); print_usage_line(0, ""); print_usage_line(1, "-reloc-mode:"); @@ -2002,6 +2469,12 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(0, ""); } + if (build) { + print_usage_line(1, "-print-linker-flags"); + print_usage_line(2, "Prints the all of the flags/arguments that will be passed to the linker."); + print_usage_line(0, ""); + } + if (check) { print_usage_line(1, "-disallow-do"); print_usage_line(2, "Disallows the 'do' keyword in the project."); @@ -2012,9 +2485,13 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(0, ""); print_usage_line(1, "-strict-style"); + print_usage_line(2, "This enforces parts of same style as the Odin compiler, prefer '-vet-style -vet-semicolon' if you do not want to match it exactly."); + print_usage_line(2, ""); print_usage_line(2, "Errs on unneeded tokens, such as unneeded semicolons."); print_usage_line(2, "Errs on missing trailing commas followed by a newline."); print_usage_line(2, "Errs on deprecated syntax."); + print_usage_line(2, "Errs when the attached-brace style in not adhered to (also known as 1TBS)."); + print_usage_line(2, "Errs when 'case' labels are not in the same column as the associated 'switch' token."); print_usage_line(0, ""); print_usage_line(1, "-ignore-warnings"); @@ -2084,6 +2561,7 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "[Windows only]"); print_usage_line(2, "Defines the resource file for the executable."); print_usage_line(2, "Example: -resource:path/to/file.rc"); + print_usage_line(2, "or: -resource:path/to/file.res for a precompiled one."); print_usage_line(0, ""); print_usage_line(1, "-pdb-name:"); @@ -2466,7 +2944,6 @@ int main(int arg_count, char const **arg_ptr) { TIME_SECTION("init args"); map_init(&build_context.defined_values); build_context.extra_packages.allocator = heap_allocator(); - string_set_init(&build_context.test_names); Array args = setup_args(arg_count, arg_ptr); @@ -2590,6 +3067,8 @@ int main(int arg_count, char const **arg_ptr) { } else if (command == "root") { gb_printf("%.*s", LIT(odin_root_dir())); return 0; + } else if (command == "clear-cache") { + return try_clear_cache() ? 0 : 1; } else { String argv1 = {}; if (args.count > 1) { @@ -2634,6 +3113,10 @@ int main(int arg_count, char const **arg_ptr) { gb_printf_err("Expected either a directory or a .odin file, got '%.*s'\n", LIT(init_filename)); return 1; } + if (!gb_file_exists(cast(const char*)init_filename.text)) { + gb_printf_err("The file '%.*s' was not found.\n", LIT(init_filename)); + return 1; + } } } } @@ -2663,7 +3146,7 @@ int main(int arg_count, char const **arg_ptr) { // Check chosen microarchitecture. If not found or ?, print list. bool print_microarch_list = true; - if (build_context.microarch.len == 0) { + if (build_context.microarch.len == 0 || build_context.microarch == str_lit("native")) { // Autodetect, no need to print list. print_microarch_list = false; } else { @@ -2680,6 +3163,11 @@ int main(int arg_count, char const **arg_ptr) { } } + // Set and check build paths... + if (!init_build_paths(init_filename)) { + return 1; + } + String default_march = get_default_microarchitecture(); if (print_microarch_list) { if (build_context.microarch != "?") { @@ -2703,13 +3191,57 @@ int main(int arg_count, char const **arg_ptr) { return 0; } - // Set and check build paths... - if (!init_build_paths(init_filename)) { - return 1; + String march = get_final_microarchitecture(); + String default_features = get_default_features(); + { + String_Iterator it = {default_features, 0}; + for (;;) { + String str = string_split_iterator(&it, ','); + if (str == "") break; + string_set_add(&build_context.target_features_set, str); + } + } + + if (build_context.target_features_string.len != 0) { + String_Iterator target_it = {build_context.target_features_string, 0}; + for (;;) { + String item = string_split_iterator(&target_it, ','); + if (item == "") break; + + String invalid; + if (!check_target_feature_is_valid_for_target_arch(item, &invalid) && item != str_lit("help")) { + if (item != str_lit("?")) { + gb_printf_err("Unkown target feature '%.*s'.\n", LIT(invalid)); + } + gb_printf("Possible -target-features for target %.*s are:\n", LIT(target_arch_names[build_context.metrics.arch])); + gb_printf("\n"); + + String feature_list = target_features_list[build_context.metrics.arch]; + String_Iterator it = {feature_list, 0}; + for (;;) { + String str = string_split_iterator(&it, ','); + if (str == "") break; + if (check_single_target_feature_is_valid(default_features, str)) { + if (has_ansi_terminal_colours()) { + gb_printf("\t%.*s\x1b[38;5;244m (implied by target microarch %.*s)\x1b[0m\n", LIT(str), LIT(march)); + } else { + gb_printf("\t%.*s (implied by current microarch %.*s)\n", LIT(str), LIT(march)); + } + } else { + gb_printf("\t%.*s\n", LIT(str)); + } + } + + return 1; + } + + string_set_add(&build_context.target_features_set, item); + } } if (build_context.show_debug_messages) { - debugf("Selected microarch: %.*s\n", LIT(default_march)); + debugf("Selected microarch: %.*s\n", LIT(march)); + debugf("Default microarch features: %.*s\n", LIT(default_features)); for_array(i, build_context.build_paths) { String build_path = path_to_string(heap_allocator(), build_context.build_paths[i]); debugf("build_paths[%ld]: %.*s\n", i, LIT(build_path)); @@ -2737,7 +3269,8 @@ int main(int arg_count, char const **arg_ptr) { // TODO(jeroen): Remove the `init_filename` param. // Let's put that on `build_context.build_paths[0]` instead. if (parse_packages(parser, init_filename) != ParseFile_None) { - return 1; + GB_ASSERT_MSG(any_errors(), "parse_packages failed but no error was reported."); + // We depend on the next conditional block to return 1, after printing errors. } if (any_errors()) { @@ -2748,13 +3281,21 @@ int main(int arg_count, char const **arg_ptr) { print_all_errors(); } - MAIN_TIME_SECTION("type check"); checker->parser = parser; init_checker(checker); - defer (destroy_checker(checker)); + defer (destroy_checker(checker)); // this is here because of a `goto` + if (build_context.cached && parser->total_seen_load_directive_count.load() == 0) { + MAIN_TIME_SECTION("check cached build (pre-semantic check)"); + if (try_cached_build(checker, args)) { + goto end_of_code_gen; + } + } + + MAIN_TIME_SECTION("type check"); check_parsed_files(checker); + check_defines(&build_context, checker); if (any_errors()) { print_all_errors(); return 1; @@ -2763,6 +3304,19 @@ int main(int arg_count, char const **arg_ptr) { print_all_errors(); } + if (build_context.show_defineables || build_context.export_defineables_file != "") { + TEMPORARY_ALLOCATOR_GUARD(); + temp_alloc_defineable_strings(checker); + sort_defineables(checker); + + if (build_context.show_defineables) { + show_defineables(checker); + } + + if (build_context.export_defineables_file != "") { + export_defineables(checker, build_context.export_defineables_file); + } + } if (build_context.command_kind == Command_strip_semicolon) { return strip_semicolons(parser); @@ -2792,6 +3346,13 @@ int main(int arg_count, char const **arg_ptr) { return 0; } + if (build_context.cached) { + MAIN_TIME_SECTION("check cached build"); + if (try_cached_build(checker, args)) { + goto end_of_code_gen; + } + } + #if ALLOW_TILDE if (build_context.tilde_backend) { LinkerData linker_data = {}; @@ -2802,12 +3363,17 @@ int main(int arg_count, char const **arg_ptr) { switch (build_context.build_mode) { case BuildMode_Executable: + case BuildMode_StaticLibrary: case BuildMode_DynamicLibrary: i32 result = linker_stage(&linker_data); if (result) { if (build_context.show_timings) { show_timings(checker, &global_timings); } + + if (build_context.export_dependencies_format != DependenciesExportUnspecified) { + export_dependencies(checker); + } return result; } break; @@ -2815,20 +3381,30 @@ int main(int arg_count, char const **arg_ptr) { } else #endif { - MAIN_TIME_SECTION("LLVM API Code Gen"); lbGenerator *gen = gb_alloc_item(permanent_allocator(), lbGenerator); if (!lb_init_generator(gen, checker)) { return 1; } + + gbString label_code_gen = gb_string_make(heap_allocator(), "LLVM API Code Gen"); + if (gen->modules.count > 1) { + label_code_gen = gb_string_append_fmt(label_code_gen, " ( %4td modules )", gen->modules.count); + } + MAIN_TIME_SECTION_WITH_LEN(label_code_gen, gb_string_length(label_code_gen)); if (lb_generate_code(gen)) { switch (build_context.build_mode) { case BuildMode_Executable: + case BuildMode_StaticLibrary: case BuildMode_DynamicLibrary: i32 result = linker_stage(gen); if (result) { if (build_context.show_timings) { show_timings(checker, &global_timings); } + + if (build_context.export_dependencies_format != DependenciesExportUnspecified) { + export_dependencies(checker); + } return result; } break; @@ -2838,15 +3414,27 @@ int main(int arg_count, char const **arg_ptr) { remove_temp_files(gen); } +end_of_code_gen:; + if (build_context.show_timings) { show_timings(checker, &global_timings); } + if (build_context.export_dependencies_format != DependenciesExportUnspecified) { + export_dependencies(checker); + } + + + if (!build_context.build_cache_data.copy_already_done && + build_context.cached) { + try_copy_executable_to_cache(); + } + if (run_output) { String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); defer (gb_free(heap_allocator(), exe_name.text)); - return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); + system_must_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); } return 0; } diff --git a/src/parser.cpp b/src/parser.cpp index f4d3dc48d..5a3fc1634 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1,7 +1,7 @@ #include "parser_pos.cpp" gb_internal u64 ast_file_vet_flags(AstFile *f) { - if (f->vet_flags_set) { + if (f != nullptr && f->vet_flags_set) { return f->vet_flags; } return build_context.vet_flags; @@ -11,6 +11,9 @@ gb_internal bool ast_file_vet_style(AstFile *f) { return (ast_file_vet_flags(f) & VetFlag_Style) != 0; } +gb_internal bool ast_file_vet_deprecated(AstFile *f) { + return (ast_file_vet_flags(f) & VetFlag_Deprecated) != 0; +} gb_internal bool file_allow_newline(AstFile *f) { bool is_strict = build_context.strict_style || ast_file_vet_style(f); @@ -32,22 +35,48 @@ gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset_) if (file == nullptr) { return nullptr; } - isize offset = pos.offset; - u8 *start = file->tokenizer.start; u8 *end = file->tokenizer.end; if (start == end) { return nullptr; } + + isize offset = pos.offset; + if (pos.line != 0 && offset == 0) { + for (i32 i = 1; i < pos.line; i++) { + while (start+offset < end) { + u8 c = start[offset++]; + if (c == '\n') { + break; + } + } + } + for (i32 i = 1; i < pos.column; i++) { + u8 *ptr = start+offset; + u8 c = *ptr; + if (c & 0x80) { + offset += utf8_decode(ptr, end-ptr, nullptr); + } else { + offset++; + } + } + } + + isize len = end-start; if (len < offset) { return nullptr; } - u8 *pos_offset = start+offset; u8 *line_start = pos_offset; u8 *line_end = pos_offset; + + if (offset > 0 && *line_start == '\n') { + // Prevent an error token that starts at the boundary of a line that + // leads to an empty line from advancing off its line. + line_start -= 1; + } while (line_start >= start) { if (*line_start == '\n') { line_start += 1; @@ -55,6 +84,11 @@ gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset_) } line_start -= 1; } + if (line_start == start - 1) { + // Prevent an error on the first line from stepping behind the boundary + // of the text. + line_start += 1; + } while (line_end < end) { if (*line_end == '\n') { @@ -67,6 +101,7 @@ gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset_) if (offset_) *offset_ = cast(i32)(pos_offset - the_line.text); + return gb_string_make_length(heap_allocator(), the_line.text, the_line.len); } @@ -77,17 +112,17 @@ gb_internal isize ast_node_size(AstKind kind) { } -gb_global std::atomic global_total_node_memory_allocated; +// gb_global std::atomic global_total_node_memory_allocated; // NOTE(bill): And this below is why is I/we need a new language! Discriminated unions are a pain in C/C++ gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind) { isize size = ast_node_size(kind); - Ast *node = cast(Ast *)arena_alloc(&global_thread_local_ast_arena, size, 16); + Ast *node = cast(Ast *)arena_alloc(get_arena(ThreadArena_Permanent), size, 16); node->kind = kind; node->file_id = f ? f->id : 0; - global_total_node_memory_allocated.fetch_add(size); + // global_total_node_memory_allocated.fetch_add(size); return node; } @@ -552,7 +587,7 @@ gb_internal Ast *ast_unary_expr(AstFile *f, Token op, Ast *expr) { syntax_error_with_verbose(expr, "'or_return' within an unary expression not wrapped in parentheses (...)"); break; case Ast_OrBranchExpr: - syntax_error_with_verbose(expr, "'or_%.*s' within an unary expression not wrapped in parentheses (...)", LIT(expr->OrBranchExpr.token.string)); + syntax_error_with_verbose(expr, "'%.*s' within an unary expression not wrapped in parentheses (...)", LIT(expr->OrBranchExpr.token.string)); break; } @@ -580,7 +615,7 @@ gb_internal Ast *ast_binary_expr(AstFile *f, Token op, Ast *left, Ast *right) { syntax_error_with_verbose(left, "'or_return' within a binary expression not wrapped in parentheses (...)"); break; case Ast_OrBranchExpr: - syntax_error_with_verbose(left, "'or_%.*s' within a binary expression not wrapped in parentheses (...)", LIT(left->OrBranchExpr.token.string)); + syntax_error_with_verbose(left, "'%.*s' within a binary expression not wrapped in parentheses (...)", LIT(left->OrBranchExpr.token.string)); break; } if (right) switch (right->kind) { @@ -588,7 +623,7 @@ gb_internal Ast *ast_binary_expr(AstFile *f, Token op, Ast *left, Ast *right) { syntax_error_with_verbose(right, "'or_return' within a binary expression not wrapped in parentheses (...)"); break; case Ast_OrBranchExpr: - syntax_error_with_verbose(right, "'or_%.*s' within a binary expression not wrapped in parentheses (...)", LIT(right->OrBranchExpr.token.string)); + syntax_error_with_verbose(right, "'%.*s' within a binary expression not wrapped in parentheses (...)", LIT(right->OrBranchExpr.token.string)); break; } @@ -715,7 +750,17 @@ gb_internal ExactValue exact_value_from_token(AstFile *f, Token const &token) { } ExactValue value = exact_value_from_basic_literal(token.kind, s); if (value.kind == ExactValue_Invalid) { - syntax_error(token, "Invalid token literal"); + switch (token.kind) { + case Token_Integer: + syntax_error(token, "Invalid integer literal"); + break; + case Token_Float: + syntax_error(token, "Invalid float literal"); + break; + default: + syntax_error(token, "Invalid token literal"); + break; + } } return value; } @@ -742,6 +787,9 @@ gb_internal Ast *ast_basic_directive(AstFile *f, Token token, Token name) { Ast *result = alloc_ast_node(f, Ast_BasicDirective); result->BasicDirective.token = token; result->BasicDirective.name = name; + if (string_starts_with(name.string, str_lit("load"))) { + f->seen_load_directive_count++; + } return result; } @@ -1281,14 +1329,16 @@ gb_internal Ast *ast_import_decl(AstFile *f, Token token, Token relpath, Token i return result; } -gb_internal Ast *ast_foreign_import_decl(AstFile *f, Token token, Array filepaths, Token library_name, - CommentGroup *docs, CommentGroup *comment) { +gb_internal Ast *ast_foreign_import_decl(AstFile *f, Token token, Array filepaths, Token library_name, + bool multiple_filepaths, + CommentGroup *docs, CommentGroup *comment) { Ast *result = alloc_ast_node(f, Ast_ForeignImportDecl); result->ForeignImportDecl.token = token; result->ForeignImportDecl.filepaths = slice_from_array(filepaths); result->ForeignImportDecl.library_name = library_name; result->ForeignImportDecl.docs = docs; result->ForeignImportDecl.comment = comment; + result->ForeignImportDecl.multiple_filepaths = multiple_filepaths; result->ForeignImportDecl.attributes.allocator = ast_allocator(f); return result; @@ -1450,7 +1500,7 @@ gb_internal bool skip_possible_newline(AstFile *f) { return false; } -gb_internal bool skip_possible_newline_for_literal(AstFile *f) { +gb_internal bool skip_possible_newline_for_literal(AstFile *f, bool ignore_strict_style=false) { Token curr = f->curr_token; if (token_is_newline(curr)) { Token next = peek_token(f); @@ -1458,6 +1508,10 @@ gb_internal bool skip_possible_newline_for_literal(AstFile *f) { switch (next.kind) { case Token_OpenBrace: case Token_else: + if (build_context.strict_style && !ignore_strict_style) { + syntax_error(next, "With '-strict-style' the attached brace style (1TBS) is enforced"); + } + /*fallthrough*/ case Token_where: advance_token(f); return true; @@ -1569,7 +1623,7 @@ gb_internal Token expect_operator(AstFile *f) { LIT(p)); } if (prev.kind == Token_Ellipsis) { - syntax_warning(prev, "'..' for ranges has now been deprecated, prefer '..='"); + syntax_error(prev, "'..' for ranges are not allowed, did you mean '..<' or '..='?"); f->tokens[f->curr_token_index].flags |= TokenFlag_Replace; } @@ -2086,6 +2140,9 @@ gb_internal bool ast_on_same_line(Token const &x, Ast *yp) { gb_internal Ast *parse_force_inlining_operand(AstFile *f, Token token) { Ast *expr = parse_unary_expr(f, false); Ast *e = strip_or_return_expr(expr); + if (e == nullptr) { + return expr; + } if (e->kind != Ast_ProcLit && e->kind != Ast_CallExpr) { syntax_error(expr, "%.*s must be followed by a procedure literal or call, got %.*s", LIT(token.string), LIT(ast_strings[expr->kind])); return ast_bad_expr(f, token, f->curr_token); @@ -2478,7 +2535,7 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { return type; } - skip_possible_newline_for_literal(f); + skip_possible_newline_for_literal(f, where_token.kind == Token_where); if (allow_token(f, Token_Uninit)) { if (where_token.kind != Token_Invalid) { @@ -3081,7 +3138,7 @@ gb_internal Ast *parse_call_expr(AstFile *f, Ast *operand) { Ast *call = ast_call_expr(f, operand, args, open_paren, close_paren, ellipsis); Ast *o = unparen_expr(operand); - if (o->kind == Ast_SelectorExpr && o->SelectorExpr.token.kind == Token_ArrowRight) { + if (o && o->kind == Ast_SelectorExpr && o->SelectorExpr.token.kind == Token_ArrowRight) { return ast_selector_call_expr(f, o->SelectorExpr.token, o, call); } @@ -3097,7 +3154,7 @@ gb_internal void parse_check_or_return(Ast *operand, char const *msg) { syntax_error_with_verbose(operand, "'or_return' use within %s is not wrapped in parentheses (...)", msg); break; case Ast_OrBranchExpr: - syntax_error_with_verbose(operand, "'or_%.*s' use within %s is not wrapped in parentheses (...)", msg, LIT(operand->OrBranchExpr.token.string)); + syntax_error_with_verbose(operand, "'%.*s' use within %s is not wrapped in parentheses (...)", msg, LIT(operand->OrBranchExpr.token.string)); break; } } @@ -3496,9 +3553,19 @@ gb_internal Array parse_ident_list(AstFile *f, bool allow_poly_names) { gb_internal Ast *parse_type(AstFile *f) { Ast *type = parse_type_or_ident(f); if (type == nullptr) { - Token token = advance_token(f); - syntax_error(token, "Expected a type"); + Token prev_token = f->curr_token; + Token token = {}; + if (f->curr_token.kind == Token_OpenBrace) { + token = f->curr_token; + } else { + token = advance_token(f); + } + syntax_error(token, "Expected a type, got '%.*s'", LIT(prev_token.string)); return ast_bad_expr(f, token, f->curr_token); + } else if (type->kind == Ast_ParenExpr && + unparen_expr(type) == nullptr) { + syntax_error(type, "Expected a type within the parentheses"); + return ast_bad_expr(f, type->ParenExpr.open, type->ParenExpr.close); } return type; } @@ -3732,8 +3799,10 @@ gb_internal Ast *parse_simple_stmt(AstFile *f, u32 flags) { case Ast_TypeSwitchStmt: stmt->TypeSwitchStmt.partial = true; break; + default: + syntax_error(partial_token, "Incorrect use of directive, use '%.*s: #partial switch'", LIT(ast_token(name).string)); + break; } - syntax_error(partial_token, "Incorrect use of directive, use '#partial %.*s: switch'", LIT(ast_token(name).string)); } else if (is_reverse) { switch (stmt->kind) { case Ast_RangeStmt: @@ -3879,10 +3948,12 @@ gb_internal Ast *parse_proc_type(AstFile *f, Token proc_token) { expect_token(f, Token_OpenParen); + f->expr_level += 1; params = parse_field_list(f, nullptr, FieldFlag_Signature, Token_CloseParen, true, true); if (file_allow_newline(f)) { skip_possible_newline(f); } + f->expr_level -= 1; expect_token_after(f, Token_CloseParen, "parameter list"); results = parse_results(f, &diverging); @@ -3943,6 +4014,7 @@ struct ParseFieldPrefixMapping { gb_global ParseFieldPrefixMapping const parse_field_prefix_mappings[] = { {str_lit("using"), Token_using, FieldFlag_using}, {str_lit("no_alias"), Token_Hash, FieldFlag_no_alias}, + {str_lit("no_capture"), Token_Hash, FieldFlag_no_capture}, {str_lit("c_vararg"), Token_Hash, FieldFlag_c_vararg}, {str_lit("const"), Token_Hash, FieldFlag_const}, {str_lit("any_int"), Token_Hash, FieldFlag_any_int}, @@ -4446,6 +4518,9 @@ gb_internal bool parse_control_statement_semicolon_separator(AstFile *f) { } + + + gb_internal Ast *parse_if_stmt(AstFile *f) { if (f->curr_proc == nullptr) { syntax_error(f->curr_token, "You cannot use an if statement in the file scope"); @@ -4488,7 +4563,11 @@ gb_internal Ast *parse_if_stmt(AstFile *f) { body = parse_block_stmt(f, false); } - skip_possible_newline_for_literal(f); + bool ignore_strict_style = false; + if (token.pos.line == ast_end_token(body).pos.line) { + ignore_strict_style = true; + } + skip_possible_newline_for_literal(f, ignore_strict_style); if (f->curr_token.kind == Token_else) { Token else_token = expect_token(f, Token_else); switch (f->curr_token.kind) { @@ -4520,9 +4599,12 @@ gb_internal Ast *parse_when_stmt(AstFile *f) { isize prev_level = f->expr_level; f->expr_level = -1; + bool prev_allow_in_expr = f->allow_in_expr; + f->allow_in_expr = true; cond = parse_expr(f, false); + f->allow_in_expr = prev_allow_in_expr; f->expr_level = prev_level; if (cond == nullptr) { @@ -4537,7 +4619,11 @@ gb_internal Ast *parse_when_stmt(AstFile *f) { body = parse_block_stmt(f, true); } - skip_possible_newline_for_literal(f); + bool ignore_strict_style = false; + if (token.pos.line == ast_end_token(body).pos.line) { + ignore_strict_style = true; + } + skip_possible_newline_for_literal(f, ignore_strict_style); if (f->curr_token.kind == Token_else) { Token else_token = expect_token(f, Token_else); switch (f->curr_token.kind) { @@ -4867,14 +4953,17 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { if (is_blank_ident(lib_name)) { syntax_error(lib_name, "Illegal foreign import name: '_'"); } - Array filepaths = {}; + bool multiple_filepaths = false; + + Array filepaths = {}; if (allow_token(f, Token_OpenBrace)) { + multiple_filepaths = true; array_init(&filepaths, ast_allocator(f)); while (f->curr_token.kind != Token_CloseBrace && f->curr_token.kind != Token_EOF) { - Token path = expect_token(f, Token_String); + Ast *path = parse_expr(f, false); array_add(&filepaths, path); if (!allow_field_separator(f)) { @@ -4883,9 +4972,10 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { } expect_closing_brace_of_field_list(f); } else { - filepaths = array_make(ast_allocator(f), 0, 1); + filepaths = array_make(ast_allocator(f), 0, 1); Token path = expect_token(f, Token_String); - array_add(&filepaths, path); + Ast *lit = ast_basic_lit(f, path); + array_add(&filepaths, lit); } Ast *s = nullptr; @@ -4894,9 +4984,9 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { s = ast_bad_decl(f, lib_name, f->curr_token); } else if (f->curr_proc != nullptr) { syntax_error(lib_name, "You cannot use foreign import within a procedure. This must be done at the file scope"); - s = ast_bad_decl(f, lib_name, filepaths[0]); + s = ast_bad_decl(f, lib_name, ast_token(filepaths[0])); } else { - s = ast_foreign_import_decl(f, token, filepaths, lib_name, docs, f->line_comment); + s = ast_foreign_import_decl(f, token, filepaths, lib_name, multiple_filepaths, docs, f->line_comment); } expect_semicolon(f); return s; @@ -5155,7 +5245,7 @@ gb_internal Ast *parse_stmt(AstFile *f) { } else if (tag == "unroll") { return parse_unrolled_for_loop(f, name); } else if (tag == "reverse") { - Ast *for_stmt = parse_for_stmt(f); + Ast *for_stmt = parse_stmt(f); if (for_stmt->kind == Ast_RangeStmt) { if (for_stmt->RangeStmt.reverse) { syntax_error(token, "#reverse already applied to a 'for in' statement"); @@ -5168,6 +5258,38 @@ gb_internal Ast *parse_stmt(AstFile *f) { } else if (tag == "include") { syntax_error(token, "#include is not a valid import declaration kind. Did you mean 'import'?"); s = ast_bad_stmt(f, token, f->curr_token); + } else if (tag == "define") { + s = ast_bad_stmt(f, token, f->curr_token); + + if (name.pos.line == f->curr_token.pos.line) { + bool call_like = false; + Ast *macro_expr = nullptr; + Token ident = f->curr_token; + if (allow_token(f, Token_Ident) && + name.pos.line == f->curr_token.pos.line) { + if (f->curr_token.kind == Token_OpenParen && f->curr_token.pos.column == ident.pos.column+ident.string.len) { + call_like = true; + (void)parse_call_expr(f, nullptr); + } + + if (name.pos.line == f->curr_token.pos.line && f->curr_token.kind != Token_Semicolon) { + macro_expr = parse_expr(f, false); + } + } + + ERROR_BLOCK(); + syntax_error(ident, "#define is not a valid declaration, Odin does not have a C-like preprocessor."); + if (macro_expr == nullptr || call_like) { + error_line("\tNote: Odin does not support macros\n"); + } else { + gbString s = expr_to_string(macro_expr); + error_line("\tSuggestion: Did you mean '%.*s :: %s'?\n", LIT(ident.string), s); + gb_string_free(s); + } + } else { + syntax_error(token, "#define is not a valid declaration, Odin does not have a C-like preprocessor."); + } + } else { syntax_error(token, "Unknown tag directive used: '%.*s'", LIT(tag)); s = ast_bad_stmt(f, token, f->curr_token); @@ -5219,12 +5341,53 @@ gb_internal Ast *parse_stmt(AstFile *f) { return ast_bad_stmt(f, token, f->curr_token); } + + +gb_internal u64 check_vet_flags(AstFile *file) { + if (file && file->vet_flags_set) { + return file->vet_flags; + } + return build_context.vet_flags; +} + + +gb_internal void parse_enforce_tabs(AstFile *f) { + Token prev = f->prev_token; + Token curr = f->curr_token; + if (prev.pos.line < curr.pos.line) { + u8 *start = f->tokenizer.start+prev.pos.offset; + u8 *end = f->tokenizer.start+curr.pos.offset; + u8 *it = end; + while (it > start) { + if (*it == '\n') { + it++; + break; + } + it--; + } + + isize len = end-it; + for (isize i = 0; i < len; i++) { + if (it[i] == ' ') { + syntax_error(curr, "With '-vet-tabs', tabs must be used for indentation"); + break; + } + } + } +} + gb_internal Array parse_stmt_list(AstFile *f) { auto list = array_make(ast_allocator(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); + } + Ast *stmt = parse_stmt(f); if (stmt && stmt->kind != Ast_EmptyStmt) { array_add(&list, stmt); @@ -5250,7 +5413,7 @@ gb_internal ParseFileError init_ast_file(AstFile *f, String const &fullpath, Tok if (!string_ends_with(f->fullpath, str_lit(".odin"))) { return ParseFile_WrongExtension; } - zero_item(&f->tokenizer); + gb_zero_item(&f->tokenizer); f->tokenizer.curr_file_id = f->id; TokenizerInitError err = init_tokenizer_from_fullpath(&f->tokenizer, f->fullpath, build_context.copy_file_contents); @@ -5381,6 +5544,7 @@ gb_internal WORKER_TASK_PROC(parser_worker_proc) { gb_internal void parser_add_file_to_process(Parser *p, AstPackage *pkg, FileInfo fi, TokenPos pos) { ImportedFile f = {pkg, fi, pos, p->file_to_process_count++}; + f.pos.file_id = cast(i32)(f.index+1); auto wd = gb_alloc_item(permanent_allocator(), ParserWorkerData); wd->parser = p; wd->imported_file = f; @@ -5417,6 +5581,7 @@ gb_internal WORKER_TASK_PROC(foreign_file_worker_proc) { gb_internal void parser_add_foreign_file_to_process(Parser *p, AstPackage *pkg, AstForeignFileKind kind, FileInfo fi, TokenPos pos) { // TODO(bill): Use a better allocator ImportedFile f = {pkg, fi, pos, p->file_to_process_count++}; + f.pos.file_id = cast(i32)(f.index+1); auto wd = gb_alloc_item(permanent_allocator(), ForeignFileWorkerData); wd->parser = p; wd->imported_file = f; @@ -5502,11 +5667,15 @@ gb_internal AstPackage *try_add_import_path(Parser *p, String path, String const } } if (files_with_ext == 0 || files_to_reserve == 1) { + ERROR_BLOCK(); if (files_with_ext != 0) { syntax_error(pos, "Directory contains no .odin files for the specified platform: %.*s", LIT(rel_path)); } else { syntax_error(pos, "Empty directory that contains no .odin files: %.*s", LIT(rel_path)); } + if (build_context.command_kind == Command_test) { + error_line("\tSuggestion: Make an .odin file that imports packages to test and use the `-all-packages` flag."); + } return nullptr; } @@ -5629,9 +5798,19 @@ gb_internal bool is_package_name_reserved(String const &name) { } -gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node, String base_dir, String const &original_string, String *path) { +gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node, String base_dir, String const &original_string, String *path, bool use_check_errors=false) { GB_ASSERT(path != nullptr); + void (*do_error)(Ast *, char const *, ...); + void (*do_warning)(Token const &, char const *, ...); + + do_error = &syntax_error; + do_warning = &syntax_warning; + if (use_check_errors) { + do_error = &error; + do_error = &warning; + } + // NOTE(bill): if file_mutex == nullptr, this means that the code is used within the semantics stage String collection_name = {}; @@ -5658,7 +5837,7 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node String file_str = {}; if (colon_pos == 0) { - syntax_error(node, "Expected a collection name"); + do_error(node, "Expected a collection name"); return false; } @@ -5673,24 +5852,41 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node if (has_windows_drive) { String sub_file_path = substring(file_str, 3, file_str.len); if (!is_import_path_valid(sub_file_path)) { - syntax_error(node, "Invalid import path: '%.*s'", LIT(file_str)); + do_error(node, "Invalid import path: '%.*s'", LIT(file_str)); return false; } } else if (!is_import_path_valid(file_str)) { - syntax_error(node, "Invalid import path: '%.*s'", LIT(file_str)); + do_error(node, "Invalid import path: '%.*s'", LIT(file_str)); return false; } - if (collection_name.len > 0) { // NOTE(bill): `base:runtime` == `core:runtime` - if (collection_name == "core" && string_starts_with(file_str, str_lit("runtime"))) { - collection_name = str_lit("base"); + if (collection_name == "core") { + bool replace_with_base = false; + if (string_starts_with(file_str, str_lit("runtime"))) { + replace_with_base = true; + } else if (string_starts_with(file_str, str_lit("intrinsics"))) { + replace_with_base = true; + } if (string_starts_with(file_str, str_lit("builtin"))) { + replace_with_base = true; + } + + if (replace_with_base) { + collection_name = str_lit("base"); + } + if (replace_with_base) { + if (ast_file_vet_deprecated(node->file())) { + do_error(node, "import \"core:%.*s\" has been deprecated in favour of \"base:%.*s\"", LIT(file_str), LIT(file_str)); + } else { + do_warning(ast_token(node), "import \"core:%.*s\" has been deprecated in favour of \"base:%.*s\"", LIT(file_str), LIT(file_str)); + } + } } if (collection_name == "system") { if (node->kind != Ast_ForeignImportDecl) { - syntax_error(node, "The library collection 'system' is restrict for 'foreign_library'"); + do_error(node, "The library collection 'system' is restrict for 'foreign import'"); return false; } else { *path = file_str; @@ -5698,7 +5894,7 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node } } else if (!find_library_collection_path(collection_name, &base_dir)) { // NOTE(bill): It's a naughty name - syntax_error(node, "Unknown library collection: '%.*s'", LIT(collection_name)); + do_error(node, "Unknown library collection: '%.*s'", LIT(collection_name)); return false; } } else { @@ -5710,7 +5906,7 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node // working directory of the exe to the library search paths. // Static libraries can be linked directly with the full pathname // - if (node->kind == Ast_ForeignImportDecl && string_ends_with(file_str, str_lit(".so"))) { + if (node->kind == Ast_ForeignImportDecl && (string_ends_with(file_str, str_lit(".so")) || string_contains_string(file_str, str_lit(".so.")))) { *path = file_str; return true; } @@ -5722,7 +5918,7 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node if (collection_name == "core" || collection_name == "base") { return true; } else { - syntax_error(node, "The package '%.*s' must be imported with the 'base' library collection: 'base:%.*s'", LIT(file_str), LIT(file_str)); + do_error(node, "The package '%.*s' must be imported with the 'base' library collection: 'base:%.*s'", LIT(file_str), LIT(file_str)); return false; } } @@ -5807,30 +6003,29 @@ gb_internal void parse_setup_file_decls(Parser *p, AstFile *f, String const &bas } else if (node->kind == Ast_ForeignImportDecl) { ast_node(fl, ForeignImportDecl, node); - auto fullpaths = array_make(permanent_allocator(), 0, fl->filepaths.count); - - for (Token const &fp : fl->filepaths) { - String file_str = string_trim_whitespace(string_value_from_token(f, fp)); + if (fl->filepaths.count == 0) { + syntax_error(decls[i], "No foreign paths found"); + decls[i] = ast_bad_decl(f, ast_token(fl->filepaths[0]), ast_end_token(fl->filepaths[fl->filepaths.count-1])); + goto end; + } else if (!fl->multiple_filepaths && + fl->filepaths.count == 1) { + Ast *fp = fl->filepaths[0]; + GB_ASSERT(fp->kind == Ast_BasicLit); + Token fp_token = fp->BasicLit.token; + String file_str = string_trim_whitespace(string_value_from_token(f, fp_token)); String fullpath = file_str; - if (allow_check_foreign_filepath()) { + if (!is_arch_wasm() || string_ends_with(fullpath, str_lit(".o"))) { String foreign_path = {}; bool ok = determine_path_from_string(&p->file_decl_mutex, node, base_dir, file_str, &foreign_path); if (!ok) { - decls[i] = ast_bad_decl(f, fp, fl->filepaths[fl->filepaths.count-1]); + decls[i] = ast_bad_decl(f, fp_token, fp_token); goto end; } fullpath = foreign_path; } - array_add(&fullpaths, fullpath); + fl->fullpaths = slice_make(permanent_allocator(), 1); + fl->fullpaths[0] = fullpath; } - if (fullpaths.count == 0) { - syntax_error(decls[i], "No foreign paths found"); - decls[i] = ast_bad_decl(f, fl->filepaths[0], fl->filepaths[fl->filepaths.count-1]); - goto end; - } - - fl->fullpaths = slice_from_array(fullpaths); - } else if (node->kind == Ast_WhenStmt) { ast_node(ws, WhenStmt, node); @@ -6183,8 +6378,6 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { } else if (lc == "+lazy") { if (build_context.ignore_lazy) { // Ignore - } else if (f->flags & AstFile_IsTest) { - // Ignore } else if (f->pkg->kind == Package_Init && build_context.command_kind == Command_doc) { // Ignore } else { @@ -6302,11 +6495,6 @@ gb_internal ParseFileError process_imported_file(Parser *p, ImportedFile importe if (build_context.command_kind == Command_test) { String name = file->fullpath; name = remove_extension_from_path(name); - - String test_suffix = str_lit("_test"); - if (string_ends_with(name, test_suffix) && name != test_suffix) { - file->flags |= AstFile_IsTest; - } } @@ -6424,6 +6612,13 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { } } } + + for (AstPackage *pkg : p->packages) { + for (AstFile *file : pkg->files) { + p->total_seen_load_directive_count += file->seen_load_directive_count; + } + } + return ParseFile_None; } diff --git a/src/parser.hpp b/src/parser.hpp index 5820275c8..565a8e621 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -74,7 +74,6 @@ enum AstFileFlag : u32 { AstFile_IsPrivatePkg = 1<<0, AstFile_IsPrivateFile = 1<<1, - AstFile_IsTest = 1<<3, AstFile_IsLazy = 1<<4, AstFile_NoInstrumentation = 1<<5, @@ -141,6 +140,8 @@ struct AstFile { // This is effectively a queue but does not require any multi-threading capabilities Array delayed_decls_queues[AstDelayQueue_COUNT]; + std::atomic seen_load_directive_count; + #define PARSER_MAX_FIX_COUNT 6 isize fix_count; TokenPos fix_prev_pos; @@ -211,6 +212,8 @@ struct Parser { std::atomic total_token_count; std::atomic total_line_count; + std::atomic total_seen_load_directive_count; + // TODO(bill): What should this mutex be per? // * Parser // * Package @@ -328,8 +331,10 @@ enum FieldFlag : u32 { FieldFlag_by_ptr = 1<<8, FieldFlag_no_broadcast = 1<<9, // disallow array programming + FieldFlag_no_capture = 1<<11, + // Internal use by the parser only - FieldFlag_Tags = 1<<10, + FieldFlag_Tags = 1<<15, FieldFlag_Results = 1<<16, @@ -337,7 +342,10 @@ enum FieldFlag : u32 { FieldFlag_Invalid = 1u<<31, // Parameter List Restrictions - FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg|FieldFlag_const|FieldFlag_any_int|FieldFlag_by_ptr|FieldFlag_no_broadcast, + FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg| + FieldFlag_const|FieldFlag_any_int|FieldFlag_by_ptr|FieldFlag_no_broadcast| + FieldFlag_no_capture, + FieldFlag_Struct = FieldFlag_using|FieldFlag_subtype|FieldFlag_Tags, }; @@ -458,6 +466,7 @@ AST_KIND(_ExprBegin, "", bool) \ bool optional_ok_one; \ bool was_selector; \ AstSplitArgs *split_args; \ + Entity *entity_procedure_of; \ }) \ AST_KIND(FieldValue, "field value", struct { Token eq; Ast *field, *value; }) \ AST_KIND(EnumFieldValue, "enum field value", struct { \ @@ -631,7 +640,8 @@ AST_KIND(_DeclBegin, "", bool) \ }) \ AST_KIND(ForeignImportDecl, "foreign import declaration", struct { \ Token token; \ - Slice filepaths; \ + Slice filepaths; \ + bool multiple_filepaths; \ Token library_name; \ String collection_name; \ Slice fullpaths; \ @@ -868,10 +878,8 @@ gb_internal gb_inline bool is_ast_when_stmt(Ast *node) { return node->kind == Ast_WhenStmt; } -gb_global gb_thread_local Arena global_thread_local_ast_arena = {}; - gb_internal gb_inline gbAllocator ast_allocator(AstFile *f) { - return arena_allocator(&global_thread_local_ast_arena); + return permanent_allocator(); } gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind); diff --git a/src/parser_pos.cpp b/src/parser_pos.cpp index b2e12999b..1ffd3a82f 100644 --- a/src/parser_pos.cpp +++ b/src/parser_pos.cpp @@ -278,7 +278,7 @@ Token ast_end_token(Ast *node) { case Ast_ImportDecl: return node->ImportDecl.relpath; case Ast_ForeignImportDecl: if (node->ForeignImportDecl.filepaths.count > 0) { - return node->ForeignImportDecl.filepaths[node->ForeignImportDecl.filepaths.count-1]; + return ast_end_token(node->ForeignImportDecl.filepaths[node->ForeignImportDecl.filepaths.count-1]); } if (node->ForeignImportDecl.library_name.kind != Token_Invalid) { return node->ForeignImportDecl.library_name; diff --git a/src/path.cpp b/src/path.cpp index b07f20870..2c08ddd98 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -152,6 +152,13 @@ gb_internal String path_to_string(gbAllocator a, Path path) { return res; } +gb_internal String quote_path(gbAllocator a, Path path) { + String temp = path_to_string(a, path); + String quoted = concatenate3_strings(a, str_lit("\""), temp, str_lit("\"")); + gb_free(a, temp.text); + return quoted; +} + // NOTE(Jeroen): Naively turns a Path into a string, then normalizes it using `path_to_full_path`. gb_internal String path_to_full_path(gbAllocator a, Path path) { String temp = path_to_string(heap_allocator(), path); @@ -341,7 +348,7 @@ gb_internal ReadDirectoryError read_directory(String path, Array *fi) return ReadDirectory_None; } -#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_HAIKU) +#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_NETBSD) || defined(GB_SYSTEM_HAIKU) #include @@ -400,16 +407,13 @@ gb_internal ReadDirectoryError read_directory(String path, Array *fi) continue; } - if (S_ISDIR(dir_stat.st_mode)) { - continue; - } - i64 size = dir_stat.st_size; FileInfo info = {}; info.name = copy_string(a, name); info.fullpath = path_to_full_path(a, filepath); info.size = size; + info.is_dir = S_ISDIR(dir_stat.st_mode); array_add(fi, info); } diff --git a/src/ptr_map.cpp b/src/ptr_map.cpp index 23278014f..1c157c386 100644 --- a/src/ptr_map.cpp +++ b/src/ptr_map.cpp @@ -9,30 +9,22 @@ enum { }; -struct MapFindResult { - MapIndex hash_index; - MapIndex entry_prev; - MapIndex entry_index; -}; - enum : MapIndex { MAP_SENTINEL = ~(MapIndex)0 }; +static void *const MAP_TOMBSTONE = (void *)~(uintptr)0; template struct PtrMapEntry { static_assert(sizeof(K) == sizeof(void *), "Key size must be pointer size"); - K key; - V value; - MapIndex next; + K key; + V value; }; template struct PtrMap { - MapIndex * hashes; - usize hashes_count; PtrMapEntry *entries; u32 count; - u32 entries_capacity; + u32 capacity; }; @@ -69,7 +61,6 @@ template gb_internal void map_grow (PtrMap< template gb_internal void map_rehash (PtrMap *h, isize new_count); template gb_internal void map_reserve (PtrMap *h, isize cap); -#if PTR_MAP_ENABLE_MULTI_MAP // Mutlivalued map procedure template gb_internal PtrMapEntry * multi_map_find_first(PtrMap *h, K key); template gb_internal PtrMapEntry * multi_map_find_next (PtrMap *h, PtrMapEntry *e); @@ -79,7 +70,6 @@ template gb_internal void multi_map_get_all (PtrMap< template gb_internal void multi_map_insert (PtrMap *h, K key, V const &value); template gb_internal void multi_map_remove (PtrMap *h, K key, PtrMapEntry *e); template gb_internal void multi_map_remove_all(PtrMap *h, K key); -#endif gb_internal gbAllocator map_allocator(void) { return heap_allocator(); @@ -94,170 +84,141 @@ gb_internal gb_inline void map_init(PtrMap *h, isize capacity) { template gb_internal gb_inline void map_destroy(PtrMap *h) { gbAllocator a = map_allocator(); - gb_free(a, h->hashes); gb_free(a, h->entries); } -template -gb_internal void map__resize_hashes(PtrMap *h, usize count) { - h->hashes_count = cast(u32)resize_array_raw(&h->hashes, map_allocator(), h->hashes_count, count, MAP_CACHE_LINE_SIZE); -} template -gb_internal void map__reserve_entries(PtrMap *h, usize capacity) { - h->entries_capacity = cast(u32)resize_array_raw(&h->entries, map_allocator(), h->entries_capacity, capacity, MAP_CACHE_LINE_SIZE); -} - - -template -gb_internal MapIndex map__add_entry(PtrMap *h, K key) { - PtrMapEntry e = {}; - e.key = key; - e.next = MAP_SENTINEL; - if (h->count+1 >= h->entries_capacity) { - map__reserve_entries(h, gb_max(h->entries_capacity*2, 4)); - } - h->entries[h->count++] = e; - return cast(MapIndex)(h->count-1); -} - -template -gb_internal MapFindResult map__find(PtrMap *h, K key) { - MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL}; - if (h->hashes_count == 0) { - return fr; +gb_internal void map__insert(PtrMap *h, K key, V const &value) { + if (h->count+1 >= h->capacity) { + map_grow(h); } u32 hash = ptr_map_hash_key(key); - fr.hash_index = cast(MapIndex)(hash & (h->hashes_count-1)); - fr.entry_index = h->hashes[fr.hash_index]; - while (fr.entry_index != MAP_SENTINEL) { - auto *entry = &h->entries[fr.entry_index]; - if (entry->key == key) { - return fr; + u32 mask = h->capacity-1; + MapIndex index = hash & mask; + MapIndex original_index = index; + do { + auto *entry = h->entries+index; + if (!entry->key || entry->key == cast(K)MAP_TOMBSTONE) { + entry->key = key; + entry->value = value; + h->count += 1; + return; } - fr.entry_prev = fr.entry_index; - fr.entry_index = entry->next; - } - return fr; -} + index = (index+1)&mask; + } while (index != original_index); -template -gb_internal MapFindResult map__find_from_entry(PtrMap *h, PtrMapEntry *e) { - MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL}; - if (h->hashes_count == 0) { - return fr; - } - u32 hash = ptr_map_hash_key(e->key); - fr.hash_index = cast(MapIndex)(hash & (h->hashes_count-1)); - fr.entry_index = h->hashes[fr.hash_index]; - while (fr.entry_index != MAP_SENTINEL) { - if (&h->entries[fr.entry_index] == e) { - return fr; - } - fr.entry_prev = fr.entry_index; - fr.entry_index = h->entries[fr.entry_index].next; - } - return fr; + GB_PANIC("FAILED TO INSERT"); } template gb_internal b32 map__full(PtrMap *h) { - return 0.75f * h->hashes_count <= h->count; + return 0.75f * h->capacity <= h->count; } template gb_internal gb_inline void map_grow(PtrMap *h) { - isize new_count = gb_max(h->hashes_count<<1, 16); - map_rehash(h, new_count); + isize new_capacity = gb_max(h->capacity<<1, 16); + map_reserve(h, new_capacity); } template -gb_internal void map_reset_entries(PtrMap *h) { - for (usize i = 0; i < h->hashes_count; i++) { - h->hashes[i] = MAP_SENTINEL; - } - for (usize i = 0; i < h->count; i++) { - MapFindResult fr; - PtrMapEntry *e = &h->entries[i]; - e->next = MAP_SENTINEL; - fr = map__find_from_entry(h, e); - if (fr.entry_prev == MAP_SENTINEL) { - h->hashes[fr.hash_index] = cast(MapIndex)i; - } else { - h->entries[fr.entry_prev].next = cast(MapIndex)i; - } +gb_internal void try_map_grow(PtrMap *h) { + if (h->capacity == 0 || map__full(h)) { + map_grow(h); } } + template gb_internal void map_reserve(PtrMap *h, isize cap) { - if (h->count*2 < h->hashes_count) { + if (cap < h->capacity) { return; } - map__reserve_entries(h, cap); - map__resize_hashes(h, cap*2); - map_reset_entries(h); -} + cap = next_pow2_isize(cap); + typedef PtrMapEntry EntryType; + PtrMap new_h = {}; + new_h.count = 0; + new_h.capacity = cast(u32)cap; + new_h.entries = gb_alloc_array(map_allocator(), EntryType, new_h.capacity); -template -gb_internal void map_rehash(PtrMap *h, isize new_count) { - map_reserve(h, new_count); + if (h->count) { + for (u32 i = 0; i < h->capacity; i++) { + auto *entry = h->entries+i; + if (entry->key && + entry->key != cast(K)MAP_TOMBSTONE) { + map__insert(&new_h, entry->key, entry->value); + } + } + } + map_destroy(h); + *h = new_h; } template gb_internal V *map_get(PtrMap *h, K key) { - MapIndex hash_index = MAP_SENTINEL; - MapIndex entry_prev = MAP_SENTINEL; - MapIndex entry_index = MAP_SENTINEL; - if (h->hashes_count != 0) { - u32 hash = ptr_map_hash_key(key); - hash_index = cast(MapIndex)(hash & (h->hashes_count-1)); - entry_index = h->hashes[hash_index]; - while (entry_index != MAP_SENTINEL) { - auto *entry = &h->entries[entry_index]; - if (entry->key == key) { - return &entry->value; - } - entry_prev = entry_index; - entry_index = entry->next; - } + if (h->count == 0) { + return nullptr; } + if (key == 0) { + GB_PANIC("0 key"); + } + + u32 hash = ptr_map_hash_key(key); + u32 mask = (h->capacity-1); + u32 index = hash & mask; + u32 original_index = index; + do { + auto *entry = h->entries+index; + if (!entry->key) { + // NOTE(bill): no found, but there isn't any key removal for this hash map + return nullptr; + } else if (entry->key == key) { + return &entry->value; + } + index = (index+1) & mask; + } while (original_index != index); return nullptr; } template -gb_internal V *map_try_get(PtrMap *h, K key, MapFindResult *fr_) { - MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL}; - if (h->hashes_count != 0) { - u32 hash = ptr_map_hash_key(key); - fr.hash_index = cast(MapIndex)(hash & (h->hashes_count-1)); - fr.entry_index = h->hashes[fr.hash_index]; - while (fr.entry_index != MAP_SENTINEL) { - auto *entry = &h->entries[fr.entry_index]; - if (entry->key == key) { - return &entry->value; - } - fr.entry_prev = fr.entry_index; - fr.entry_index = entry->next; +gb_internal V *map_try_get(PtrMap *h, K key, MapIndex *found_index_) { + if (found_index_) *found_index_ = ~(MapIndex)0; + + if (h->count == 0) { + return nullptr; + } + if (key == 0) { + GB_PANIC("0 key"); + } + + u32 hash = ptr_map_hash_key(key); + u32 mask = (h->capacity-1); + u32 index = hash & mask; + u32 original_index = index; + do { + auto *entry = h->entries+index; + if (!entry->key) { + // NOTE(bill): no found, but there isn't any key removal for this hash map + return nullptr; + } else if (entry->key == key) { + if (found_index_) *found_index_ = index; + return &entry->value; } - } - if (h->hashes_count == 0 || map__full(h)) { - map_grow(h); - } - if (fr_) *fr_ = fr; + index = (index+1) & mask; + } while (original_index != index); return nullptr; } template -gb_internal void map_set_internal_from_try_get(PtrMap *h, K key, V const &value, MapFindResult const &fr) { - MapIndex index = map__add_entry(h, key); - if (fr.entry_prev != MAP_SENTINEL) { - h->entries[fr.entry_prev].next = index; +gb_internal void map_set_internal_from_try_get(PtrMap *h, K key, V const &value, MapIndex found_index) { + if (found_index != MAP_SENTINEL) { + GB_ASSERT(h->entries[found_index].key == key); + h->entries[found_index].value = value; } else { - h->hashes[fr.hash_index] = index; + map_set(h, key, value); } - h->entries[index].value = value; } template @@ -269,116 +230,83 @@ gb_internal V &map_must_get(PtrMap *h, K key) { template gb_internal void map_set(PtrMap *h, K key, V const &value) { - MapIndex index; - MapFindResult fr; - if (h->hashes_count == 0) { - map_grow(h); - } - fr = map__find(h, key); - if (fr.entry_index != MAP_SENTINEL) { - index = fr.entry_index; - } else { - index = map__add_entry(h, key); - if (fr.entry_prev != MAP_SENTINEL) { - h->entries[fr.entry_prev].next = index; - } else { - h->hashes[fr.hash_index] = index; - } - } - h->entries[index].value = value; - - if (map__full(h)) { - map_grow(h); + GB_ASSERT(key != 0); + try_map_grow(h); + auto *found = map_get(h, key); + if (found) { + *found = value; + return; } + map__insert(h, key, value); } // returns true if it previously existed template gb_internal bool map_set_if_not_previously_exists(PtrMap *h, K key, V const &value) { - MapIndex index; - MapFindResult fr; - if (h->hashes_count == 0) { - map_grow(h); - } - fr = map__find(h, key); - if (fr.entry_index != MAP_SENTINEL) { + try_map_grow(h); + auto *found = map_get(h, key); + if (found) { return true; - } else { - index = map__add_entry(h, key); - if (fr.entry_prev != MAP_SENTINEL) { - h->entries[fr.entry_prev].next = index; - } else { - h->hashes[fr.hash_index] = index; - } - } - h->entries[index].value = value; - - if (map__full(h)) { - map_grow(h); } + map__insert(h, key, value); return false; } -template -gb_internal void map__erase(PtrMap *h, MapFindResult const &fr) { - MapFindResult last; - if (fr.entry_prev == MAP_SENTINEL) { - h->hashes[fr.hash_index] = h->entries[fr.entry_index].next; - } else { - h->entries[fr.entry_prev].next = h->entries[fr.entry_index].next; - } - if (fr.entry_index == h->count-1) { - h->count--; - return; - } - h->entries[fr.entry_index] = h->entries[h->count-1]; - h->count--; - - last = map__find(h, h->entries[fr.entry_index].key); - if (last.entry_prev != MAP_SENTINEL) { - h->entries[last.entry_prev].next = fr.entry_index; - } else { - h->hashes[last.hash_index] = fr.entry_index; - } -} - template gb_internal void map_remove(PtrMap *h, K key) { - MapFindResult fr = map__find(h, key); - if (fr.entry_index != MAP_SENTINEL) { - map__erase(h, fr); + MapIndex found_index = 0; + if (map_try_get(h, key, &found_index)) { + h->entries[found_index].key = cast(K)MAP_TOMBSTONE; + h->count -= 1; } } template gb_internal gb_inline void map_clear(PtrMap *h) { h->count = 0; - for (usize i = 0; i < h->hashes_count; i++) { - h->hashes[i] = MAP_SENTINEL; - } + gb_zero_array(h->entries, h->capacity); } #if PTR_MAP_ENABLE_MULTI_MAP template gb_internal PtrMapEntry *multi_map_find_first(PtrMap *h, K key) { - MapIndex i = map__find(h, key).entry_index; - if (i == MAP_SENTINEL) { + if (h->count == 0) { return nullptr; } - return &h->entries[i]; + u32 hash = ptr_map_hash_key(key); + u32 mask = (h->capacity-1); + u32 index = hash & mask; + u32 original_index = index; + do { + auto *entry = h->entries+index; + if (!entry->key) { + // NOTE(bill): no found, but there isn't any key removal for this hash map + return nullptr; + } else if (entry->key == key) { + return entry; + } + index = (index+1) & mask; + } while (original_index != index); + return nullptr; } template gb_internal PtrMapEntry *multi_map_find_next(PtrMap *h, PtrMapEntry *e) { - MapIndex i = e->next; - while (i != MAP_SENTINEL) { - if (h->entries[i].key == e->key) { - return &h->entries[i]; + u32 mask = h->capacity-1; + MapIndex index = cast(MapIndex)(e - h->entries); + MapIndex original_index = index; + do { + index = (index+1)&mask; + auto *entry = h->entries+index; + if (!entry->key) { + return nullptr; } - i = h->entries[i].next; - } + if (entry->key == e->key) { + return entry; + } + } while (original_index != index); return nullptr; } @@ -405,6 +333,476 @@ gb_internal void multi_map_get_all(PtrMap *h, K key, V *items) { template gb_internal void multi_map_insert(PtrMap *h, K key, V const &value) { + try_map_grow(h); + map__insert(h, key, value); +} + +// template +// gb_internal void multi_map_remove(PtrMap *h, K key, PtrMapEntry *e) { +// if (fr.entry_index != MAP_SENTINEL) { +// map__erase(h, fr); +// } +// } + +template +gb_internal void multi_map_remove_all(PtrMap *h, K key) { + while (map_get(h, key) != nullptr) { + map_remove(h, key); + } +} +#endif + + + + +template +struct PtrMapIterator { + PtrMap *map; + MapIndex index; + + PtrMapIterator &operator++() noexcept { + for (;;) { + ++index; + if (map->capacity == index) { + return *this; + } + PtrMapEntry *entry = map->entries+index; + if (entry->key && entry->key != cast(K)MAP_TOMBSTONE) { + return *this; + } + } + } + + bool operator==(PtrMapIterator const &other) const noexcept { + return this->map == other->map && this->index == other->index; + } + + operator PtrMapEntry *() const { + return map->entries+index; + } +}; + + +template +gb_internal PtrMapIterator end(PtrMap &m) noexcept { + return PtrMapIterator{&m, m.capacity}; +} + +template +gb_internal PtrMapIterator const end(PtrMap const &m) noexcept { + return PtrMapIterator{&m, m.capacity}; +} + + + +template +gb_internal PtrMapIterator begin(PtrMap &m) noexcept { + if (m.count == 0) { + return end(m); + } + + MapIndex index = 0; + while (index < m.capacity) { + auto key = m.entries[index].key; + if (key && key != cast(K)MAP_TOMBSTONE) { + break; + } + index++; + } + return PtrMapIterator{&m, index}; +} +template +gb_internal PtrMapIterator const begin(PtrMap const &m) noexcept { + if (m.count == 0) { + return end(m); + } + + MapIndex index = 0; + while (index < m.capacity) { + auto key = m.entries[index].key; + if (key && key != cast(K)MAP_TOMBSTONE) { + break; + } + index++; + } + return PtrMapIterator{&m, index}; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +struct MapFindResult { + MapIndex hash_index; + MapIndex entry_prev; + MapIndex entry_index; +}; + +template +struct OrderedInsertPtrMapEntry { + static_assert(sizeof(K) == sizeof(void *), "Key size must be pointer size"); + + K key; + V value; + MapIndex next; +}; + +template +struct OrderedInsertPtrMap { + MapIndex *hashes; + usize hashes_count; + OrderedInsertPtrMapEntry *entries; + u32 count; + u32 entries_capacity; +}; + + +template gb_internal void map_destroy (OrderedInsertPtrMap *h); +template gb_internal V * map_get (OrderedInsertPtrMap *h, K key); +template gb_internal void map_set (OrderedInsertPtrMap *h, K key, V const &value); +template gb_internal bool map_set_if_not_previously_exists(OrderedInsertPtrMap *h, K key, V const &value); // returns true if it previously existed +template gb_internal void map_remove (OrderedInsertPtrMap *h, K key); +template gb_internal void map_clear (OrderedInsertPtrMap *h); +template gb_internal void map_grow (OrderedInsertPtrMap *h); +template gb_internal void map_rehash (OrderedInsertPtrMap *h, isize new_count); +template gb_internal void map_reserve (OrderedInsertPtrMap *h, isize cap); + +// Mutlivalued map procedure +template gb_internal OrderedInsertPtrMapEntry * multi_map_find_first(OrderedInsertPtrMap *h, K key); +template gb_internal OrderedInsertPtrMapEntry * multi_map_find_next (OrderedInsertPtrMap *h, OrderedInsertPtrMapEntry *e); + +template gb_internal isize multi_map_count (OrderedInsertPtrMap *h, K key); +template gb_internal void multi_map_get_all (OrderedInsertPtrMap *h, K key, V *items); +template gb_internal void multi_map_insert (OrderedInsertPtrMap *h, K key, V const &value); +template gb_internal void multi_map_remove (OrderedInsertPtrMap *h, K key, OrderedInsertPtrMapEntry *e); +template gb_internal void multi_map_remove_all(OrderedInsertPtrMap *h, K key); + +template +gb_internal gb_inline void map_init(OrderedInsertPtrMap *h, isize capacity) { + capacity = next_pow2_isize(capacity); + map_reserve(h, capacity); +} + +template +gb_internal gb_inline void map_destroy(OrderedInsertPtrMap *h) { + gbAllocator a = map_allocator(); + gb_free(a, h->hashes); + gb_free(a, h->entries); +} + +template +gb_internal void map__resize_hashes(OrderedInsertPtrMap *h, usize count) { + h->hashes_count = cast(u32)resize_array_raw(&h->hashes, map_allocator(), h->hashes_count, count, MAP_CACHE_LINE_SIZE); +} + +template +gb_internal void map__reserve_entries(OrderedInsertPtrMap *h, usize capacity) { + h->entries_capacity = cast(u32)resize_array_raw(&h->entries, map_allocator(), h->entries_capacity, capacity, MAP_CACHE_LINE_SIZE); +} + + +template +gb_internal MapIndex map__add_entry(OrderedInsertPtrMap *h, K key) { + OrderedInsertPtrMapEntry e = {}; + e.key = key; + e.next = MAP_SENTINEL; + if (h->count+1 >= h->entries_capacity) { + map__reserve_entries(h, gb_max(h->entries_capacity*2, 4)); + } + h->entries[h->count++] = e; + return cast(MapIndex)(h->count-1); +} + +template +gb_internal MapFindResult map__find(OrderedInsertPtrMap *h, K key) { + MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL}; + if (h->hashes_count == 0) { + return fr; + } + u32 hash = ptr_map_hash_key(key); + fr.hash_index = cast(MapIndex)(hash & (h->hashes_count-1)); + fr.entry_index = h->hashes[fr.hash_index]; + while (fr.entry_index != MAP_SENTINEL) { + auto *entry = &h->entries[fr.entry_index]; + if (entry->key == key) { + return fr; + } + fr.entry_prev = fr.entry_index; + fr.entry_index = entry->next; + } + return fr; +} + +template +gb_internal MapFindResult map__find_from_entry(OrderedInsertPtrMap *h, OrderedInsertPtrMapEntry *e) { + MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL}; + if (h->hashes_count == 0) { + return fr; + } + u32 hash = ptr_map_hash_key(e->key); + fr.hash_index = cast(MapIndex)(hash & (h->hashes_count-1)); + fr.entry_index = h->hashes[fr.hash_index]; + while (fr.entry_index != MAP_SENTINEL) { + if (&h->entries[fr.entry_index] == e) { + return fr; + } + fr.entry_prev = fr.entry_index; + fr.entry_index = h->entries[fr.entry_index].next; + } + return fr; +} + +template +gb_internal b32 map__full(OrderedInsertPtrMap *h) { + return 0.75f * h->hashes_count <= h->count; +} + +template +gb_internal gb_inline void map_grow(OrderedInsertPtrMap *h) { + isize new_count = gb_max(h->hashes_count<<1, 16); + map_rehash(h, new_count); +} + +template +gb_internal void map_reset_entries(OrderedInsertPtrMap *h) { + for (usize i = 0; i < h->hashes_count; i++) { + h->hashes[i] = MAP_SENTINEL; + } + for (usize i = 0; i < h->count; i++) { + MapFindResult fr; + OrderedInsertPtrMapEntry *e = &h->entries[i]; + e->next = MAP_SENTINEL; + fr = map__find_from_entry(h, e); + if (fr.entry_prev == MAP_SENTINEL) { + h->hashes[fr.hash_index] = cast(MapIndex)i; + } else { + h->entries[fr.entry_prev].next = cast(MapIndex)i; + } + } +} + +template +gb_internal void map_reserve(OrderedInsertPtrMap *h, isize cap) { + if (h->count*2 < h->hashes_count) { + return; + } + map__reserve_entries(h, cap); + map__resize_hashes(h, cap*2); + map_reset_entries(h); +} + + +template +gb_internal void map_rehash(OrderedInsertPtrMap *h, isize new_count) { + map_reserve(h, new_count); +} + +template +gb_internal V *map_get(OrderedInsertPtrMap *h, K key) { + MapIndex hash_index = MAP_SENTINEL; + MapIndex entry_prev = MAP_SENTINEL; + MapIndex entry_index = MAP_SENTINEL; + if (h->hashes_count != 0) { + u32 hash = ptr_map_hash_key(key); + hash_index = cast(MapIndex)(hash & (h->hashes_count-1)); + entry_index = h->hashes[hash_index]; + while (entry_index != MAP_SENTINEL) { + auto *entry = &h->entries[entry_index]; + if (entry->key == key) { + return &entry->value; + } + entry_prev = entry_index; + entry_index = entry->next; + } + } + return nullptr; +} +template +gb_internal V *map_try_get(OrderedInsertPtrMap *h, K key, MapFindResult *fr_) { + MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL}; + if (h->hashes_count != 0) { + u32 hash = ptr_map_hash_key(key); + fr.hash_index = cast(MapIndex)(hash & (h->hashes_count-1)); + fr.entry_index = h->hashes[fr.hash_index]; + while (fr.entry_index != MAP_SENTINEL) { + auto *entry = &h->entries[fr.entry_index]; + if (entry->key == key) { + return &entry->value; + } + fr.entry_prev = fr.entry_index; + fr.entry_index = entry->next; + } + } + if (h->hashes_count == 0 || map__full(h)) { + map_grow(h); + } + if (fr_) *fr_ = fr; + return nullptr; +} + + +template +gb_internal void map_set_internal_from_try_get(OrderedInsertPtrMap *h, K key, V const &value, MapFindResult const &fr) { + MapIndex index = map__add_entry(h, key); + if (fr.entry_prev != MAP_SENTINEL) { + h->entries[fr.entry_prev].next = index; + } else { + h->hashes[fr.hash_index] = index; + } + h->entries[index].value = value; +} + +template +gb_internal V &map_must_get(OrderedInsertPtrMap *h, K key) { + V *ptr = map_get(h, key); + GB_ASSERT(ptr != nullptr); + return *ptr; +} + +template +gb_internal void map_set(OrderedInsertPtrMap *h, K key, V const &value) { + MapIndex index; + MapFindResult fr; + if (h->hashes_count == 0) { + map_grow(h); + } + fr = map__find(h, key); + if (fr.entry_index != MAP_SENTINEL) { + index = fr.entry_index; + } else { + index = map__add_entry(h, key); + if (fr.entry_prev != MAP_SENTINEL) { + h->entries[fr.entry_prev].next = index; + } else { + h->hashes[fr.hash_index] = index; + } + } + h->entries[index].value = value; + + if (map__full(h)) { + map_grow(h); + } +} + +// returns true if it previously existed +template +gb_internal bool map_set_if_not_previously_exists(OrderedInsertPtrMap *h, K key, V const &value) { + MapIndex index; + MapFindResult fr; + if (h->hashes_count == 0) { + map_grow(h); + } + fr = map__find(h, key); + if (fr.entry_index != MAP_SENTINEL) { + return true; + } else { + index = map__add_entry(h, key); + if (fr.entry_prev != MAP_SENTINEL) { + h->entries[fr.entry_prev].next = index; + } else { + h->hashes[fr.hash_index] = index; + } + } + h->entries[index].value = value; + + if (map__full(h)) { + map_grow(h); + } + return false; +} + + +template +gb_internal void map__erase(OrderedInsertPtrMap *h, MapFindResult const &fr) { + MapFindResult last; + if (fr.entry_prev == MAP_SENTINEL) { + h->hashes[fr.hash_index] = h->entries[fr.entry_index].next; + } else { + h->entries[fr.entry_prev].next = h->entries[fr.entry_index].next; + } + if (fr.entry_index == h->count-1) { + h->count--; + return; + } + h->entries[fr.entry_index] = h->entries[h->count-1]; + h->count--; + + last = map__find(h, h->entries[fr.entry_index].key); + if (last.entry_prev != MAP_SENTINEL) { + h->entries[last.entry_prev].next = fr.entry_index; + } else { + h->hashes[last.hash_index] = fr.entry_index; + } +} + +template +gb_internal void map_remove(OrderedInsertPtrMap *h, K key) { + MapFindResult fr = map__find(h, key); + if (fr.entry_index != MAP_SENTINEL) { + map__erase(h, fr); + } +} + +template +gb_internal gb_inline void map_clear(OrderedInsertPtrMap *h) { + h->count = 0; + for (usize i = 0; i < h->hashes_count; i++) { + h->hashes[i] = MAP_SENTINEL; + } +} + + +template +gb_internal OrderedInsertPtrMapEntry *multi_map_find_first(OrderedInsertPtrMap *h, K key) { + MapIndex i = map__find(h, key).entry_index; + if (i == MAP_SENTINEL) { + return nullptr; + } + return &h->entries[i]; +} + +template +gb_internal OrderedInsertPtrMapEntry *multi_map_find_next(OrderedInsertPtrMap *h, OrderedInsertPtrMapEntry *e) { + MapIndex i = e->next; + while (i != MAP_SENTINEL) { + if (h->entries[i].key == e->key) { + return &h->entries[i]; + } + i = h->entries[i].next; + } + return nullptr; +} + +template +gb_internal isize multi_map_count(OrderedInsertPtrMap *h, K key) { + isize count = 0; + OrderedInsertPtrMapEntry *e = multi_map_find_first(h, key); + while (e != nullptr) { + count++; + e = multi_map_find_next(h, e); + } + return count; +} + +template +gb_internal void multi_map_get_all(OrderedInsertPtrMap *h, K key, V *items) { + usize i = 0; + OrderedInsertPtrMapEntry *e = multi_map_find_first(h, key); + while (e != nullptr) { + items[i++] = e->value; + e = multi_map_find_next(h, e); + } +} + +template +gb_internal void multi_map_insert(OrderedInsertPtrMap *h, K key, V const &value) { MapFindResult fr; MapIndex i; if (h->hashes_count == 0) { @@ -427,7 +825,7 @@ gb_internal void multi_map_insert(PtrMap *h, K key, V const &value) { } template -gb_internal void multi_map_remove(PtrMap *h, K key, PtrMapEntry *e) { +gb_internal void multi_map_remove(OrderedInsertPtrMap *h, K key, OrderedInsertPtrMapEntry *e) { MapFindResult fr = map__find_from_entry(h, e); if (fr.entry_index != MAP_SENTINEL) { map__erase(h, fr); @@ -435,30 +833,29 @@ gb_internal void multi_map_remove(PtrMap *h, K key, PtrMapEntry *e) } template -gb_internal void multi_map_remove_all(PtrMap *h, K key) { +gb_internal void multi_map_remove_all(OrderedInsertPtrMap *h, K key) { while (map_get(h, key) != nullptr) { map_remove(h, key); } } -#endif template -gb_internal PtrMapEntry *begin(PtrMap &m) { +gb_internal OrderedInsertPtrMapEntry *begin(OrderedInsertPtrMap &m) { return m.entries; } template -gb_internal PtrMapEntry const *begin(PtrMap const &m) { +gb_internal OrderedInsertPtrMapEntry const *begin(OrderedInsertPtrMap const &m) { return m.entries; } template -gb_internal PtrMapEntry *end(PtrMap &m) { +gb_internal OrderedInsertPtrMapEntry *end(OrderedInsertPtrMap &m) { return m.entries + m.count; } template -gb_internal PtrMapEntry const *end(PtrMap const &m) { +gb_internal OrderedInsertPtrMapEntry const *end(OrderedInsertPtrMap const &m) { return m.entries + m.count; -} +} \ No newline at end of file diff --git a/src/queue.cpp b/src/queue.cpp index 2ad9cb29f..dee9ad1f8 100644 --- a/src/queue.cpp +++ b/src/queue.cpp @@ -16,7 +16,7 @@ struct MPSCQueue { std::atomic count; }; -template gb_internal void mpsc_init (MPSCQueue *q); +template gb_internal void mpsc_init (MPSCQueue *q, gbAllocator const &allocator); template gb_internal void mpsc_destroy(MPSCQueue *q); template gb_internal isize mpsc_enqueue(MPSCQueue *q, T const &value); template gb_internal bool mpsc_dequeue(MPSCQueue *q, T *value_); diff --git a/src/string.cpp b/src/string.cpp index b92dd589e..3c7d96934 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -88,6 +88,13 @@ gb_internal char *alloc_cstring(gbAllocator a, String s) { return c_str; } +gb_internal wchar_t *alloc_wstring(gbAllocator a, String16 s) { + wchar_t *c_str = gb_alloc_array(a, wchar_t, s.len+1); + gb_memmove(c_str, s.text, s.len*2); + c_str[s.len] = '\0'; + return c_str; +} + gb_internal gb_inline bool str_eq_ignore_case(String const &a, String const &b) { if (a.len == b.len) { @@ -328,6 +335,25 @@ gb_internal bool string_contains_char(String const &s, u8 c) { return false; } +gb_internal bool string_contains_string(String const &haystack, String const &needle) { + if (needle.len == 0) return true; + if (needle.len > haystack.len) return false; + + for (isize i = 0; i <= haystack.len - needle.len; i++) { + bool found = true; + for (isize j = 0; j < needle.len; j++) { + if (haystack[i + j] != needle[j]) { + found = false; + break; + } + } + if (found) { + return true; + } + } + return false; +} + gb_internal String filename_from_path(String s) { isize i = string_extension_position(s); if (i >= 0) { @@ -524,6 +550,40 @@ gb_internal String string16_to_string(gbAllocator a, String16 s) { +gb_internal String temporary_directory(gbAllocator allocator) { +#if defined(GB_SYSTEM_WINDOWS) + DWORD n = GetTempPathW(0, nullptr); + if (n == 0) { + return String{0}; + } + DWORD len = gb_max(MAX_PATH, n); + wchar_t *b = gb_alloc_array(heap_allocator(), wchar_t, len+1); + defer (gb_free(heap_allocator(), b)); + n = GetTempPathW(len, b); + if (n == 3 && b[1] == ':' && b[2] == '\\') { + + } else if (n > 0 && b[n-1] == '\\') { + n -= 1; + } + b[n] = 0; + String16 s = make_string16(b, n); + return string16_to_string(allocator, s); +#else + char const *tmp_env = gb_get_env("TMPDIR", allocator); + if (tmp_env) { + return make_string_c(tmp_env); + } + +#if defined(P_tmpdir) + String tmp_macro = make_string_c(P_tmpdir); + if (tmp_macro.len != 0) { + return copy_string(allocator, tmp_macro); + } +#endif + + return copy_string(allocator, str_lit("/tmp")); +#endif +} diff --git a/src/string_map.cpp b/src/string_map.cpp index f8b86a950..802bf5853 100644 --- a/src/string_map.cpp +++ b/src/string_map.cpp @@ -2,8 +2,8 @@ GB_STATIC_ASSERT(sizeof(MapIndex) == sizeof(u32)); struct StringHashKey { - u32 hash; String string; + u32 hash; operator String() const noexcept { return this->string; @@ -13,7 +13,8 @@ struct StringHashKey { } }; gb_internal gb_inline u32 string_hash(String const &s) { - return fnv32a(s.text, s.len) & 0x7fffffff; + u32 res = fnv32a(s.text, s.len) & 0x7fffffff; + return res | (res == 0); } gb_internal gb_inline StringHashKey string_hash_string(String const &s) { @@ -23,6 +24,9 @@ gb_internal gb_inline StringHashKey string_hash_string(String const &s) { return hash_key; } + +#if 1 /* old string map */ + template struct StringMapEntry { String key; @@ -278,36 +282,6 @@ gb_internal gb_inline void string_map_set(StringMap *h, StringHashKey const & } - -// template -// gb_internal void string_map__erase(StringMap *h, MapFindResult const &fr) { -// MapFindResult last; -// if (fr.entry_prev == MAP_SENTINEL) { -// h->hashes[fr.hash_index] = h->entries[fr.entry_index].next; -// } else { -// h->entries[fr.entry_prev].next = h->entries[fr.entry_index].next; -// } -// if (fr.entry_index == h->count-1) { -// array_pop(&h->entries); -// return; -// } -// h->entries[fr.entry_index] = h->entries[h->count-1]; -// last = string_map__find(h, h->entries[fr.entry_index].key); -// if (last.entry_prev != MAP_SENTINEL) { -// h->entries[last.entry_prev].next = fr.entry_index; -// } else { -// h->hashes[last.hash_index] = fr.entry_index; -// } -// } - -// template -// gb_internal void string_map_remove(StringMap *h, StringHashKey const &key) { -// MapFindResult fr = string_map__find(h, key); -// if (fr.entry_index != MAP_SENTINEL) { -// string_map__erase(h, fr); -// } -// } - template gb_internal gb_inline void string_map_clear(StringMap *h) { h->count = 0; @@ -329,11 +303,297 @@ gb_internal StringMapEntry const *begin(StringMap const &m) noexcept { template -gb_internal StringMapEntry *end(StringMap &m) { +gb_internal StringMapEntry *end(StringMap &m) noexcept { return m.entries + m.count; } template gb_internal StringMapEntry const *end(StringMap const &m) noexcept { return m.entries + m.count; -} \ No newline at end of file +} + +#else /* new string map */ + +template +struct StringMapEntry { + String key; + u32 hash; + T value; +}; + +template +struct StringMap { + StringMapEntry *entries; + u32 count; + u32 capacity; +}; + + +template gb_internal void string_map_init (StringMap *h, usize capacity = 16); +template gb_internal void string_map_destroy (StringMap *h); + +template gb_internal T * string_map_get (StringMap *h, char const *key); +template gb_internal T * string_map_get (StringMap *h, String const &key); +template gb_internal T * string_map_get (StringMap *h, StringHashKey const &key); + +template gb_internal T & string_map_must_get(StringMap *h, char const *key); +template gb_internal T & string_map_must_get(StringMap *h, String const &key); +template gb_internal T & string_map_must_get(StringMap *h, StringHashKey const &key); + +template gb_internal void string_map_set (StringMap *h, char const *key, T const &value); +template gb_internal void string_map_set (StringMap *h, String const &key, T const &value); +template gb_internal void string_map_set (StringMap *h, StringHashKey const &key, T const &value); + +// template gb_internal void string_map_remove (StringMap *h, StringHashKey const &key); +template gb_internal void string_map_clear (StringMap *h); +template gb_internal void string_map_grow (StringMap *h); +template gb_internal void string_map_reserve (StringMap *h, usize new_count); + +gb_internal gbAllocator string_map_allocator(void) { + return heap_allocator(); +} + +template +gb_internal gb_inline void string_map_init(StringMap *h, usize capacity) { + capacity = next_pow2_isize(capacity); + string_map_reserve(h, capacity); +} + +template +gb_internal gb_inline void string_map_destroy(StringMap *h) { + gb_free(string_map_allocator(), h->entries); +} + + +template +gb_internal void string_map__insert(StringMap *h, u32 hash, String const &key, T const &value) { + if (h->count+1 >= h->capacity) { + string_map_grow(h); + } + GB_ASSERT(h->count+1 < h->capacity); + + u32 mask = h->capacity-1; + MapIndex index = hash & mask; + MapIndex original_index = index; + do { + auto *entry = h->entries+index; + if (entry->hash == 0) { + entry->key = key; + entry->hash = hash; + entry->value = value; + + h->count += 1; + return; + } + index = (index+1)&mask; + } while (index != original_index); + + GB_PANIC("Full map"); +} + +template +gb_internal b32 string_map__full(StringMap *h) { + return 0.75f * h->count <= h->capacity; +} + +template +gb_inline void string_map_grow(StringMap *h) { + isize new_capacity = gb_max(h->capacity<<1, 16); + string_map_reserve(h, new_capacity); +} + + +template +gb_internal void string_map_reserve(StringMap *h, usize cap) { + if (cap < h->capacity) { + return; + } + cap = next_pow2_isize(cap); + + StringMap new_h = {}; + new_h.count = 0; + new_h.capacity = cast(u32)cap; + new_h.entries = gb_alloc_array(string_map_allocator(), StringMapEntry, new_h.capacity); + + if (h->count) { + for (u32 i = 0; i < h->capacity; i++) { + auto *entry = h->entries+i; + if (entry->hash) { + string_map__insert(&new_h, entry->hash, entry->key, entry->value); + } + } + } + string_map_destroy(h); + *h = new_h; +} + +template +gb_internal T *string_map_get(StringMap *h, u32 hash, String const &key) { + if (h->count == 0) { + return nullptr; + } + u32 mask = (h->capacity-1); + u32 index = hash & mask; + u32 original_index = index; + do { + auto *entry = h->entries+index; + u32 curr_hash = entry->hash; + if (curr_hash == 0) { + // NOTE(bill): no found, but there isn't any key removal for this hash map + return nullptr; + } else if (curr_hash == hash && entry->key == key) { + return &entry->value; + } + index = (index+1) & mask; + } while (original_index != index); + return nullptr; +} + + +template +gb_internal gb_inline T *string_map_get(StringMap *h, StringHashKey const &key) { + return string_map_get(h, key.hash, key.string); +} + +template +gb_internal gb_inline T *string_map_get(StringMap *h, String const &key) { + return string_map_get(h, string_hash(key), key); +} + +template +gb_internal gb_inline T *string_map_get(StringMap *h, char const *key) { + String k = make_string_c(key); + return string_map_get(h, string_hash(k), k); +} + +template +gb_internal T &string_map_must_get(StringMap *h, u32 hash, String const &key) { + T *found = string_map_get(h, hash, key); + GB_ASSERT(found != nullptr); + return *found; +} + +template +gb_internal T &string_map_must_get(StringMap *h, StringHashKey const &key) { + return string_map_must_get(h, key.hash, key.string); +} + +template +gb_internal gb_inline T &string_map_must_get(StringMap *h, String const &key) { + return string_map_must_get(h, string_hash(key), key); +} + +template +gb_internal gb_inline T &string_map_must_get(StringMap *h, char const *key) { + String k = make_string_c(key); + return string_map_must_get(h, string_hash(k), k); +} + +template +gb_internal void string_map_set(StringMap *h, u32 hash, String const &key, T const &value) { + if (h->count == 0) { + string_map_grow(h); + } + auto *found = string_map_get(h, hash, key); + if (found) { + *found = value; + return; + } + string_map__insert(h, hash, key, value); +} + +template +gb_internal gb_inline void string_map_set(StringMap *h, String const &key, T const &value) { + string_map_set(h, string_hash_string(key), value); +} + +template +gb_internal gb_inline void string_map_set(StringMap *h, char const *key, T const &value) { + string_map_set(h, string_hash_string(make_string_c(key)), value); +} + +template +gb_internal gb_inline void string_map_set(StringMap *h, StringHashKey const &key, T const &value) { + string_map_set(h, key.hash, key.string, value); +} + + +template +gb_internal gb_inline void string_map_clear(StringMap *h) { + h->count = 0; + gb_zero_array(h->entries, h->capacity); +} + + +template +struct StringMapIterator { + StringMap *map; + MapIndex index; + + StringMapIterator &operator++() noexcept { + for (;;) { + ++index; + if (map->capacity == index) { + return *this; + } + StringMapEntry *entry = map->entries+index; + if (entry->hash != 0) { + return *this; + } + } + } + + bool operator==(StringMapIterator const &other) const noexcept { + return this->map == other->map && this->index == other->index; + } + + operator StringMapEntry *() const { + return map->entries+index; + } +}; + + +template +gb_internal StringMapIterator end(StringMap &m) noexcept { + return StringMapIterator{&m, m.capacity}; +} + +template +gb_internal StringMapIterator const end(StringMap const &m) noexcept { + return StringMapIterator{&m, m.capacity}; +} + + + +template +gb_internal StringMapIterator begin(StringMap &m) noexcept { + if (m.count == 0) { + return end(m); + } + + MapIndex index = 0; + while (index < m.capacity) { + if (m.entries[index].hash) { + break; + } + index++; + } + return StringMapIterator{&m, index}; +} +template +gb_internal StringMapIterator const begin(StringMap const &m) noexcept { + if (m.count == 0) { + return end(m); + } + + MapIndex index = 0; + while (index < m.capacity) { + if (m.entries[index].hash) { + break; + } + index++; + } + return StringMapIterator{&m, index}; +} + +#endif \ No newline at end of file diff --git a/src/string_set.cpp b/src/string_set.cpp index fb4640c20..a37d8ba80 100644 --- a/src/string_set.cpp +++ b/src/string_set.cpp @@ -208,7 +208,9 @@ gb_internal void string_set__erase(StringSet *s, MapFindResult fr) { } auto *entry = &s->entries[fr.entry_index]; *entry = s->entries[s->entries.count-1]; - StringHashKey key = {entry->hash, entry->value}; + StringHashKey key; + key.hash = entry->hash; + key.string = entry->value; last = string_set__find(s, key); if (last.entry_prev != MAP_SENTINEL) { s->entries[last.entry_prev].next = fr.entry_index; diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp index 5dbbe37c4..8363a4553 100644 --- a/src/thread_pool.cpp +++ b/src/thread_pool.cpp @@ -3,20 +3,28 @@ struct WorkerTask; struct ThreadPool; -gb_thread_local Thread *current_thread; +gb_global gb_thread_local Thread *current_thread; +gb_internal Thread *get_current_thread(void) { + return current_thread; +} gb_internal void thread_pool_init(ThreadPool *pool, isize worker_count, char const *worker_name); gb_internal void thread_pool_destroy(ThreadPool *pool); gb_internal bool thread_pool_add_task(ThreadPool *pool, WorkerTaskProc *proc, void *data); gb_internal void thread_pool_wait(ThreadPool *pool); +enum GrabState { + Grab_Success = 0, + Grab_Empty = 1, + Grab_Failed = 2, +}; + struct ThreadPool { - gbAllocator threads_allocator; - Slice threads; + gbAllocator threads_allocator; + Slice threads; std::atomic running; Futex tasks_available; - Futex tasks_left; }; @@ -46,7 +54,7 @@ gb_internal void thread_pool_destroy(ThreadPool *pool) { for_array_off(i, 1, pool->threads) { Thread *t = &pool->threads[i]; - pool->tasks_available.fetch_add(1, std::memory_order_relaxed); + pool->tasks_available.fetch_add(1, std::memory_order_acquire); futex_broadcast(&pool->tasks_available); thread_join_and_destroy(t); } @@ -54,51 +62,86 @@ gb_internal void thread_pool_destroy(ThreadPool *pool) { gb_free(pool->threads_allocator, pool->threads.data); } +TaskRingBuffer *task_ring_grow(TaskRingBuffer *ring, isize bottom, isize top) { + TaskRingBuffer *new_ring = task_ring_init(ring->size * 2); + for (isize i = top; i < bottom; i++) { + new_ring->buffer[i % new_ring->size] = ring->buffer[i % ring->size]; + } + return new_ring; +} + void thread_pool_queue_push(Thread *thread, WorkerTask task) { - u64 capture; - u64 new_capture; - do { - capture = thread->head_and_tail.load(); + isize bot = thread->queue.bottom.load(std::memory_order_relaxed); + isize top = thread->queue.top.load(std::memory_order_acquire); + TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_relaxed); - u64 mask = thread->capacity - 1; - u64 head = (capture >> 32) & mask; - u64 tail = ((u32)capture) & mask; + isize size = bot - top; + if (size > (cur_ring->size - 1)) { + // Queue is full + thread->queue.ring = task_ring_grow(thread->queue.ring, bot, top); + cur_ring = thread->queue.ring.load(std::memory_order_relaxed); + } - u64 new_head = (head + 1) & mask; - GB_ASSERT_MSG(new_head != tail, "Thread Queue Full!"); - - // This *must* be done in here, to avoid a potential race condition where we no longer own the slot by the time we're assigning - thread->queue[head] = task; - new_capture = (new_head << 32) | tail; - } while (!thread->head_and_tail.compare_exchange_weak(capture, new_capture)); + cur_ring->buffer[bot % cur_ring->size] = task; + std::atomic_thread_fence(std::memory_order_release); + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); thread->pool->tasks_left.fetch_add(1, std::memory_order_release); thread->pool->tasks_available.fetch_add(1, std::memory_order_relaxed); futex_broadcast(&thread->pool->tasks_available); } -bool thread_pool_queue_pop(Thread *thread, WorkerTask *task) { - u64 capture; - u64 new_capture; - do { - capture = thread->head_and_tail.load(std::memory_order_acquire); +GrabState thread_pool_queue_take(Thread *thread, WorkerTask *task) { + isize bot = thread->queue.bottom.load(std::memory_order_relaxed) - 1; + TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_relaxed); + thread->queue.bottom.store(bot, std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_seq_cst); - u64 mask = thread->capacity - 1; - u64 head = (capture >> 32) & mask; - u64 tail = ((u32)capture) & mask; + isize top = thread->queue.top.load(std::memory_order_relaxed); + if (top <= bot) { - u64 new_tail = (tail + 1) & mask; - if (tail == head) { - return false; + // Queue is not empty + *task = cur_ring->buffer[bot % cur_ring->size]; + if (top == bot) { + // Only one entry left in queue + if (!thread->queue.top.compare_exchange_strong(top, top + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { + // Race failed + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); + return Grab_Empty; + } + + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); + return Grab_Success; } - // Making a copy of the task before we increment the tail, avoiding the same potential race condition as above - *task = thread->queue[tail]; + // We got a task without hitting a race + return Grab_Success; + } else { + // Queue is empty + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); + return Grab_Empty; + } +} - new_capture = (head << 32) | new_tail; - } while (!thread->head_and_tail.compare_exchange_weak(capture, new_capture, std::memory_order_release)); +GrabState thread_pool_queue_steal(Thread *thread, WorkerTask *task) { + isize top = thread->queue.top.load(std::memory_order_acquire); + std::atomic_thread_fence(std::memory_order_seq_cst); + isize bot = thread->queue.bottom.load(std::memory_order_acquire); - return true; + GrabState ret = Grab_Empty; + if (top < bot) { + // Queue is not empty + TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_consume); + *task = cur_ring->buffer[top % cur_ring->size]; + + if (!thread->queue.top.compare_exchange_strong(top, top + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { + // Race failed + ret = Grab_Failed; + } else { + ret = Grab_Success; + } + } + return ret; } gb_internal bool thread_pool_add_task(ThreadPool *pool, WorkerTaskProc *proc, void *data) { @@ -115,12 +158,11 @@ gb_internal void thread_pool_wait(ThreadPool *pool) { while (pool->tasks_left.load(std::memory_order_acquire)) { // if we've got tasks on our queue, run them - while (thread_pool_queue_pop(current_thread, &task)) { + while (!thread_pool_queue_take(current_thread, &task)) { task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); } - // is this mem-barriered enough? // This *must* be executed in this order, so the futex wakes immediately // if rem_tasks has changed since we checked last, otherwise the program @@ -145,7 +187,7 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { usize finished_tasks = 0; i32 state; - while (thread_pool_queue_pop(current_thread, &task)) { + while (!thread_pool_queue_take(current_thread, &task)) { task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); @@ -167,7 +209,12 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { Thread *thread = &pool->threads.data[idx]; WorkerTask task; - if (thread_pool_queue_pop(thread, &task)) { + + GrabState ret = thread_pool_queue_steal(thread, &task); + switch (ret) { + case Grab_Empty: + continue; + case Grab_Success: task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); @@ -175,6 +222,8 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { futex_signal(&pool->tasks_left); } + /*fallthrough*/ + case Grab_Failed: goto main_loop_continue; } } @@ -182,6 +231,7 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { // if we've done all our work, and there's nothing to steal, go to sleep state = pool->tasks_available.load(std::memory_order_acquire); + if (!pool->running) { break; } futex_wait(&pool->tasks_available, state); main_loop_continue:; diff --git a/src/threading.cpp b/src/threading.cpp index fbe8997d1..011b66028 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -46,6 +46,18 @@ typedef struct WorkerTask { void *data; } WorkerTask; +typedef struct TaskRingBuffer { + std::atomic size; + std::atomic buffer; +} TaskRingBuffer; + +typedef struct TaskQueue { + std::atomic top; + std::atomic bottom; + + std::atomic ring; +} TaskQueue; + struct Thread { #if defined(GB_SYSTEM_WINDOWS) void *win32_handle; @@ -54,13 +66,13 @@ struct Thread { #endif isize idx; + isize stack_size; - WorkerTask *queue; - size_t capacity; - std::atomic head_and_tail; - - isize stack_size; + struct TaskQueue queue; struct ThreadPool *pool; + + struct Arena *permanent_arena; + struct Arena *temporary_arena; }; typedef std::atomic Futex; @@ -492,6 +504,10 @@ gb_internal u32 thread_current_id(void) { thread_id = gettid(); #elif defined(GB_SYSTEM_HAIKU) thread_id = find_thread(NULL); +#elif defined(GB_SYSTEM_FREEBSD) + thread_id = pthread_getthreadid_np(); +#elif defined(GB_SYSTEM_NETBSD) + thread_id = (u32)_lwp_self(); #else #error Unsupported architecture for thread_current_id() #endif @@ -547,6 +563,20 @@ gb_internal void *internal_thread_proc(void *arg) { } #endif +gb_internal TaskRingBuffer *task_ring_init(isize size) { + TaskRingBuffer *ring = gb_alloc_item(heap_allocator(), TaskRingBuffer); + ring->size = size; + ring->buffer = gb_alloc_array(heap_allocator(), WorkerTask, ring->size); + return ring; +} + +gb_internal void thread_queue_destroy(TaskQueue *q) { + gb_free(heap_allocator(), (*q->ring).buffer); + gb_free(heap_allocator(), q->ring); +} + +gb_internal void thread_init_arenas(Thread *t); + gb_internal void thread_init(ThreadPool *pool, Thread *t, isize idx) { gb_zero_item(t); #if defined(GB_SYSTEM_WINDOWS) @@ -555,13 +585,13 @@ gb_internal void thread_init(ThreadPool *pool, Thread *t, isize idx) { t->posix_handle = 0; #endif - t->capacity = 1 << 14; // must be a power of 2 - t->queue = gb_alloc_array(heap_allocator(), WorkerTask, t->capacity); - t->head_and_tail = 0; + // Size must be a power of 2 + t->queue.ring = task_ring_init(1 << 14); t->pool = pool; t->idx = idx; -} + thread_init_arenas(t); +} gb_internal void thread_init_and_start(ThreadPool *pool, Thread *t, isize idx) { thread_init(pool, t, idx); @@ -594,7 +624,7 @@ gb_internal void thread_join_and_destroy(Thread *t) { t->posix_handle = 0; #endif - gb_free(heap_allocator(), t->queue); + thread_queue_destroy(&t->queue); } gb_internal void thread_set_name(Thread *t, char const *name) { @@ -626,15 +656,23 @@ gb_internal void thread_set_name(Thread *t, char const *name) { pthread_setname_np(name); #elif defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) pthread_set_name_np(t->posix_handle, name); +#elif defined(GB_SYSTEM_NETBSD) + pthread_setname_np(t->posix_handle, "%s", (void*)name); #else // TODO(bill): Test if this works pthread_setname_np(t->posix_handle, name); #endif } -#if defined(GB_SYSTEM_LINUX) -#include +#if defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_NETBSD) + #include +#ifdef GB_SYSTEM_LINUX + #include +#else + #include + #define SYS_futex SYS___futex +#endif gb_internal void futex_signal(Futex *addr) { int ret = syscall(SYS_futex, addr, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1, NULL, NULL, 0); @@ -758,13 +796,27 @@ gb_internal void futex_wait(Futex *f, Footex val) { #elif defined(GB_SYSTEM_OSX) +// IMPORTANT NOTE(laytan): We use `OS_SYNC_*_SHARED` and `UL_COMPARE_AND_WAIT_SHARED` flags here. +// these flags tell the kernel that we are using these futexes across different processes which +// causes it to opt-out of some optimisations. +// +// BUT this is not actually the case! We should be using the normal non-shared version and letting +// the kernel optimize (I've measured it to be about 10% faster at the parsing/type checking stages). +// +// However we have reports of people on MacOS running into kernel panics, and this seems to fix it for them. +// Which means there is probably a bug in the kernel in one of these non-shared optimisations causing the panic. +// +// The panic also doesn't seem to happen on normal M1 CPUs, and happen more on later CPUs or pro/max series. +// Probably because they have more going on in terms of threads etc. + #if __has_include() #define DARWIN_WAIT_ON_ADDRESS_AVAILABLE #include #endif -#define UL_COMPARE_AND_WAIT 0x00000001 -#define ULF_NO_ERRNO 0x01000000 +#define UL_COMPARE_AND_WAIT 0x00000001 +#define UL_COMPARE_AND_WAIT_SHARED 0x00000003 +#define ULF_NO_ERRNO 0x01000000 extern "C" int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint32_t timeout); /* timeout is specified in microseconds */ extern "C" int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value); @@ -773,7 +825,7 @@ gb_internal void futex_signal(Futex *f) { #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE if (__builtin_available(macOS 14.4, *)) { for (;;) { - int ret = os_sync_wake_by_address_any(f, sizeof(Futex), OS_SYNC_WAKE_BY_ADDRESS_NONE); + int ret = os_sync_wake_by_address_any(f, sizeof(Futex), OS_SYNC_WAKE_BY_ADDRESS_SHARED); if (ret >= 0) { return; } @@ -788,7 +840,7 @@ gb_internal void futex_signal(Futex *f) { } else { #endif for (;;) { - int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0); + int ret = __ulock_wake(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO, f, 0); if (ret >= 0) { return; } @@ -809,7 +861,7 @@ gb_internal void futex_broadcast(Futex *f) { #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE if (__builtin_available(macOS 14.4, *)) { for (;;) { - int ret = os_sync_wake_by_address_all(f, sizeof(Footex), OS_SYNC_WAKE_BY_ADDRESS_NONE); + int ret = os_sync_wake_by_address_all(f, sizeof(Footex), OS_SYNC_WAKE_BY_ADDRESS_SHARED); if (ret >= 0) { return; } @@ -825,7 +877,7 @@ gb_internal void futex_broadcast(Futex *f) { #endif for (;;) { enum { ULF_WAKE_ALL = 0x00000100 }; - int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0); + int ret = __ulock_wake(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0); if (ret == 0) { return; } @@ -846,7 +898,7 @@ gb_internal void futex_wait(Futex *f, Footex val) { #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE if (__builtin_available(macOS 14.4, *)) { for (;;) { - int ret = os_sync_wait_on_address(f, cast(uint64_t)(val), sizeof(Footex), OS_SYNC_WAIT_ON_ADDRESS_NONE); + int ret = os_sync_wait_on_address(f, cast(uint64_t)(val), sizeof(Footex), OS_SYNC_WAIT_ON_ADDRESS_SHARED); if (ret >= 0) { if (*f != val) { return; @@ -864,7 +916,7 @@ gb_internal void futex_wait(Futex *f, Footex val) { } else { #endif for (;;) { - int ret = __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, val, 0); + int ret = __ulock_wait(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO, f, val, 0); if (ret >= 0) { if (*f != val) { return; diff --git a/src/tilde.cpp b/src/tilde.cpp index 4fc7d1c9b..f6fed0f9a 100644 --- a/src/tilde.cpp +++ b/src/tilde.cpp @@ -363,7 +363,6 @@ gb_internal bool cg_global_variables_create(cgModule *m, Array if (is_foreign) { linkage = TB_LINKAGE_PUBLIC; // lb_add_foreign_library_path(m, e->Variable.foreign_library); - // lb_set_wasm_import_attributes(g.value, e, name); } else if (is_export) { linkage = TB_LINKAGE_PUBLIC; } diff --git a/src/timings.cpp b/src/timings.cpp index 712e804cb..3f8402b36 100644 --- a/src/timings.cpp +++ b/src/timings.cpp @@ -146,9 +146,10 @@ gb_internal f64 time_stamp_as_us(TimeStamp const &ts, u64 freq) { return 1000000.0*time_stamp_as_s(ts, freq); } -#define MAIN_TIME_SECTION(str) do { debugf("[Section] %s\n", str); timings_start_section(&global_timings, str_lit(str)); } while (0) -#define TIME_SECTION(str) do { debugf("[Section] %s\n", str); if (build_context.show_more_timings) timings_start_section(&global_timings, str_lit(str)); } while (0) -#define TIME_SECTION_WITH_LEN(str, len) do { debugf("[Section] %s\n", str); if (build_context.show_more_timings) timings_start_section(&global_timings, make_string((u8 *)str, len)); } while (0) +#define MAIN_TIME_SECTION(str) do { debugf("[Section] %s\n", str); timings_start_section(&global_timings, str_lit(str)); } while (0) +#define MAIN_TIME_SECTION_WITH_LEN(str, len) do { debugf("[Section] %s\n", str); timings_start_section(&global_timings, make_string((u8 *)str, len)); } while (0) +#define TIME_SECTION(str) do { debugf("[Section] %s\n", str); if (build_context.show_more_timings) timings_start_section(&global_timings, str_lit(str)); } while (0) +#define TIME_SECTION_WITH_LEN(str, len) do { debugf("[Section] %s\n", str); if (build_context.show_more_timings) timings_start_section(&global_timings, make_string((u8 *)str, len)); } while (0) enum TimingUnit { diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index fdff9224a..4425bee29 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -432,7 +432,10 @@ gb_internal gb_inline i32 digit_value(Rune r) { return 16; // NOTE(bill): Larger than highest possible } -gb_internal gb_inline void scan_mantissa(Tokenizer *t, i32 base) { +gb_internal gb_inline void scan_mantissa(Tokenizer *t, i32 base, bool force_base) { + if (!force_base) { + base = 16; // always check for any possible letter + } while (digit_value(t->curr_rune) < base || t->curr_rune == '_') { advance_to_next_rune(t); } @@ -457,7 +460,7 @@ gb_internal void scan_number_to_token(Tokenizer *t, Token *token, bool seen_deci token->string.len += 1; token->pos.column -= 1; token->kind = Token_Float; - scan_mantissa(t, 10); + scan_mantissa(t, 10, true); goto exponent; } @@ -467,44 +470,50 @@ gb_internal void scan_number_to_token(Tokenizer *t, Token *token, bool seen_deci switch (t->curr_rune) { case 'b': // Binary advance_to_next_rune(t); - scan_mantissa(t, 2); + scan_mantissa(t, 2, false); if (t->curr - prev <= 2) { + tokenizer_err(t, "Invalid binary integer"); token->kind = Token_Invalid; } goto end; case 'o': // Octal advance_to_next_rune(t); - scan_mantissa(t, 8); + scan_mantissa(t, 8, false); if (t->curr - prev <= 2) { + tokenizer_err(t, "Invalid octal integer"); token->kind = Token_Invalid; } goto end; case 'd': // Decimal advance_to_next_rune(t); - scan_mantissa(t, 10); + scan_mantissa(t, 10, false); if (t->curr - prev <= 2) { + tokenizer_err(t, "Invalid explicitly decimal integer"); token->kind = Token_Invalid; } goto end; case 'z': // Dozenal advance_to_next_rune(t); - scan_mantissa(t, 12); + scan_mantissa(t, 12, false); if (t->curr - prev <= 2) { + tokenizer_err(t, "Invalid dozenal integer"); token->kind = Token_Invalid; } goto end; case 'x': // Hexadecimal advance_to_next_rune(t); - scan_mantissa(t, 16); + scan_mantissa(t, 16, false); if (t->curr - prev <= 2) { + tokenizer_err(t, "Invalid hexadecimal integer"); token->kind = Token_Invalid; } goto end; case 'h': // Hexadecimal Float token->kind = Token_Float; advance_to_next_rune(t); - scan_mantissa(t, 16); + scan_mantissa(t, 16, false); if (t->curr - prev <= 2) { + tokenizer_err(t, "Invalid hexadecimal float"); token->kind = Token_Invalid; } else { u8 *start = prev+2; @@ -527,12 +536,12 @@ gb_internal void scan_number_to_token(Tokenizer *t, Token *token, bool seen_deci } goto end; default: - scan_mantissa(t, 10); + scan_mantissa(t, 10, true); goto fraction; } } - scan_mantissa(t, 10); + scan_mantissa(t, 10, true); fraction: @@ -544,7 +553,7 @@ fraction: advance_to_next_rune(t); token->kind = Token_Float; - scan_mantissa(t, 10); + scan_mantissa(t, 10, true); } exponent: @@ -554,7 +563,7 @@ exponent: if (t->curr_rune == '-' || t->curr_rune == '+') { advance_to_next_rune(t); } - scan_mantissa(t, 10); + scan_mantissa(t, 10, false); } switch (t->curr_rune) { @@ -767,9 +776,8 @@ gb_internal void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) { } } - // TODO(bill): Better Error Handling if (valid && n != 1) { - tokenizer_err(t, "Invalid rune literal"); + tokenizer_err(t, token->pos, "Invalid rune literal"); } token->string.len = t->curr - token->string.text; goto semicolon_check; @@ -778,7 +786,6 @@ gb_internal void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) { case '`': // Raw String Literal case '"': // String Literal { - bool has_carriage_return = false; i32 success; Rune quote = curr_rune; token->kind = Token_String; @@ -808,9 +815,6 @@ gb_internal void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) { if (r == quote) { break; } - if (r == '\r') { - has_carriage_return = true; - } } } token->string.len = t->curr - token->string.text; diff --git a/src/types.cpp b/src/types.cpp index 18cb12ea1..3f86d4c50 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -140,6 +140,7 @@ struct TypeStruct { i64 custom_field_align; Type * polymorphic_params; // Type_Tuple Type * polymorphic_parent; + Wait_Signal polymorphic_wait_signal; Type * soa_elem; i32 soa_count; @@ -167,6 +168,7 @@ struct TypeUnion { i64 custom_align; Type * polymorphic_params; // Type_Tuple Type * polymorphic_parent; + Wait_Signal polymorphic_wait_signal; i16 tag_size; bool is_polymorphic; @@ -184,6 +186,8 @@ struct TypeProc { isize specialization_count; ProcCallingConvention calling_convention; i32 variadic_index; + String require_target_feature; + String enable_target_feature; // TODO(bill): Make this a flag set rather than bools bool variadic; bool require_results; @@ -455,6 +459,15 @@ gb_internal Selection sub_selection(Selection const &sel, isize offset) { return res; } +gb_internal Selection trim_selection(Selection const &sel) { + Selection res = {}; + res.index.data = sel.index.data; + res.index.count = gb_max(sel.index.count - 1, 0); + res.index.capacity = res.index.count; + return res; +} + + gb_global Type basic_types[] = { {Type_Basic, {Basic_Invalid, 0, 0, STR_LIT("invalid type")}}, @@ -951,7 +964,7 @@ gb_internal Type *alloc_type(TypeKind kind) { // gbAllocator a = heap_allocator(); gbAllocator a = permanent_allocator(); Type *t = gb_alloc_item(a, Type); - zero_item(t); + gb_zero_item(t); t->kind = kind; t->cached_size = -1; t->cached_align = -1; @@ -986,6 +999,27 @@ gb_internal Type *alloc_type_soa_pointer(Type *elem) { return t; } +gb_internal Type *alloc_type_pointer_to_multi_pointer(Type *ptr) { + Type *original_type = ptr; + ptr = base_type(ptr); + if (ptr->kind == Type_Pointer) { + return alloc_type_multi_pointer(ptr->Pointer.elem); + } else if (ptr->kind != Type_MultiPointer) { + GB_PANIC("Invalid type: %s", type_to_string(original_type)); + } + return original_type; +} + +gb_internal Type *alloc_type_multi_pointer_to_pointer(Type *ptr) { + Type *original_type = ptr; + ptr = base_type(ptr); + if (ptr->kind == Type_MultiPointer) { + return alloc_type_pointer(ptr->MultiPointer.elem); + } else if (ptr->kind != Type_Pointer) { + GB_PANIC("Invalid type: %s", type_to_string(original_type)); + } + return original_type; +} gb_internal Type *alloc_type_array(Type *elem, i64 count, Type *generic_count = nullptr) { if (generic_count != nullptr) { @@ -1061,6 +1095,7 @@ gb_internal Type *alloc_type_struct() { gb_internal Type *alloc_type_struct_complete() { Type *t = alloc_type(Type_Struct); wait_signal_set(&t->Struct.fields_wait_signal); + wait_signal_set(&t->Struct.polymorphic_wait_signal); return t; } @@ -1459,10 +1494,10 @@ gb_internal i64 matrix_align_of(Type *t, struct TypePath *tp) { i64 total_expected_size = row_count*t->Matrix.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 % min_alignment) != 0) { + while (total_expected_size != 0 && (total_expected_size % min_alignment) != 0) { min_alignment >>= 1; } - GB_ASSERT(min_alignment >= elem_align); + min_alignment = gb_max(min_alignment, elem_align); i64 align = gb_min(min_alignment, build_context.max_simd_align); return align; @@ -1976,6 +2011,24 @@ gb_internal bool is_type_valid_bit_set_elem(Type *t) { return false; } + +gb_internal bool is_valid_bit_field_backing_type(Type *type) { + if (type == nullptr) { + return false; + } + type = base_type(type); + if (is_type_untyped(type)) { + return false; + } + if (is_type_integer(type)) { + return true; + } + if (type->kind == Type_Array) { + return is_type_integer(type->Array.elem); + } + return false; +} + gb_internal Type *bit_set_to_int(Type *t) { GB_ASSERT(is_type_bit_set(t)); Type *bt = base_type(t); @@ -1983,6 +2036,9 @@ gb_internal Type *bit_set_to_int(Type *t) { if (underlying != nullptr && is_type_integer(underlying)) { return underlying; } + if (underlying != nullptr && is_valid_bit_field_backing_type(underlying)) { + return underlying; + } i64 sz = type_size_of(t); switch (sz) { @@ -2098,21 +2154,24 @@ gb_internal bool is_type_polymorphic_record_unspecialized(Type *t) { t = base_type(t); if (t->kind == Type_Struct) { return t->Struct.is_polymorphic && !t->Struct.is_poly_specialized; - } else if (t->kind == Type_Struct) { - return t->Struct.is_polymorphic && !t->Struct.is_poly_specialized; + } else if (t->kind == Type_Union) { + return t->Union.is_polymorphic && !t->Union.is_poly_specialized; } return false; } + gb_internal TypeTuple *get_record_polymorphic_params(Type *t) { t = base_type(t); switch (t->kind) { case Type_Struct: + wait_signal_until_available(&t->Struct.polymorphic_wait_signal); if (t->Struct.polymorphic_params) { return &t->Struct.polymorphic_params->Tuple; } break; case Type_Union: + wait_signal_until_available(&t->Union.polymorphic_wait_signal); if (t->Union.polymorphic_params) { return &t->Union.polymorphic_params->Tuple; } @@ -2328,6 +2387,7 @@ gb_internal bool type_has_nil(Type *t) { return false; } + gb_internal bool elem_type_can_be_constant(Type *t) { t = base_type(t); if (t == t_invalid) { @@ -2884,11 +2944,14 @@ gb_internal Type *c_vararg_promote_type(Type *type) { if (core->kind == Type_Basic) { switch (core->Basic.kind) { + case Basic_f16: case Basic_f32: case Basic_UntypedFloat: return t_f64; + case Basic_f16le: case Basic_f32le: return t_f64le; + case Basic_f16be: case Basic_f32be: return t_f64be; @@ -2991,7 +3054,22 @@ gb_internal Type *union_tag_type(Type *u) { return t_uint; } +gb_internal int matched_target_features(TypeProc *t) { + if (t->require_target_feature.len == 0) { + return 0; + } + int matches = 0; + String_Iterator it = {t->require_target_feature, 0}; + for (;;) { + String str = string_split_iterator(&it, ','); + if (str == "") break; + if (check_target_feature_is_valid_for_target_arch(str, nullptr)) { + matches += 1; + } + } + return matches; +} enum ProcTypeOverloadKind { ProcOverload_Identical, // The types are identical @@ -3003,6 +3081,7 @@ enum ProcTypeOverloadKind { ProcOverload_ResultCount, ProcOverload_ResultTypes, ProcOverload_Polymorphic, + ProcOverload_TargetFeatures, ProcOverload_NotProcedure, @@ -3060,6 +3139,10 @@ gb_internal ProcTypeOverloadKind are_proc_types_overload_safe(Type *x, Type *y) } } + if (matched_target_features(&px) != matched_target_features(&py)) { + return ProcOverload_TargetFeatures; + } + if (px.params != nullptr && py.params != nullptr) { Entity *ex = px.params->Tuple.variables[0]; Entity *ey = py.params->Tuple.variables[0]; @@ -3166,7 +3249,7 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name } } if (type->kind == Type_Struct) { - wait_signal_until_available(&type->Struct.fields_wait_signal); + // wait_signal_until_available(&type->Struct.fields_wait_signal); isize field_count = type->Struct.fields.count; if (field_count != 0) for_array(i, type->Struct.fields) { Entity *f = type->Struct.fields[i]; @@ -3196,7 +3279,7 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name } if (type->kind == Type_Struct) { - wait_signal_until_available(&type->Struct.fields_wait_signal); + // wait_signal_until_available(&type->Struct.fields_wait_signal); Scope *s = type->Struct.scope; if (s != nullptr) { Entity *found = scope_lookup_current(s, field_name); @@ -3245,6 +3328,10 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name } } + if (is_type_polymorphic(type)) { + // NOTE(bill): A polymorphic struct has no fields, this only hits in the case of an error + return sel; + } wait_signal_until_available(&type->Struct.fields_wait_signal); isize field_count = type->Struct.fields.count; if (field_count != 0) for_array(i, type->Struct.fields) { diff --git a/src/ucg/ucg.c b/src/ucg/ucg.c new file mode 100644 index 000000000..c3e270e1a --- /dev/null +++ b/src/ucg/ucg.c @@ -0,0 +1,686 @@ +/* + * SPDX-FileCopyrightText: (c) 2024 Feoramund + * SPDX-License-Identifier: BSD-3-Clause + */ + + +// +// NOTE(Feoramund): This is my UCG library, adapted for use within the Odin compiler. +// Most of the comments have been let alone and may not strictly apply anymore. +// +// 1. The UCG allocator interface was replaced by gbAllocator. +// 2. The UCG UTF-8 decoder was replaced with the one already in the compiler. +// 3. Non-essential code was stripped. +// 4. Some types were changed for compatibility. +// + + +/* This is the data that is allocated when an allocator is passed to + * ucg_decode_grapheme_clusters. */ +typedef struct { + i32 byte_index; + i32 rune_index; + i32 width; +} ucg_grapheme; + + +/* #include "ucg.h" */ +#include "ucg_tables.h" + +#define UCG_TABLE_LEN(t) (sizeof(t) / sizeof(int32_t)) + +#define ZERO_WIDTH_SPACE 0x200B +#define ZERO_WIDTH_NON_JOINER 0x200C +#define ZERO_WIDTH_JOINER 0x200D +#define WORD_JOINER 0x2060 + +int ucg_binary_search(int32_t value, const int32_t* table, int length, int stride) { + GB_ASSERT(table != NULL); + GB_ASSERT(length > 0); + GB_ASSERT(stride > 0); + + int n = length; + int t = 0; + for (/**/; n > 1; /**/) { + int m = n / 2; + int p = t + m * stride; + if (value >= table[p]) { + t = p; + n = n - m; + } else { + n = m; + } + } + if (n != 0 && value >= table[t]) { + return t; + } + return -1; +} + +// +// The procedures below are accurate as of Unicode 15.1.0. +// + +bool ucg_is_control(int32_t r) { + if (r <= 0x1F || (0x7F <= r && r <= 0x9F)) { + return true; + } + return false; +} + +// Emoji_Modifier +bool ucg_is_emoji_modifier(int32_t r) { + return 0x1F3FB <= r && r <= 0x1F3FF; +} + +// Regional_Indicator +bool ucg_is_regional_indicator(int32_t r) { + return 0x1F1E6 <= r && r <= 0x1F1FF; +} + +// General_Category=Enclosing_Mark +bool ucg_is_enclosing_mark(int32_t r) { + switch (r) { + case 0x0488: + case 0x0489: + case 0x1ABE: + return true; + } + + if (0x20DD <= r && r <= 0x20E0) { return true; } + if (0x20E2 <= r && r <= 0x20E4) { return true; } + if (0xA670 <= r && r <= 0xA672) { return true; } + + return false; +} + +// Prepended_Concatenation_Mark +bool ucg_is_prepended_concatenation_mark(int32_t r) { + switch (r) { + case 0x006DD: + case 0x0070F: + case 0x008E2: + case 0x110BD: + case 0x110CD: + return true; + } + + if (0x00600 <= r && r <= 0x00605) { return true; } + if (0x00890 <= r && r <= 0x00891) { return true; } + + return false; +} + +// General_Category=Spacing_Mark +bool ucg_is_spacing_mark(int32_t r) { + intptr_t p = ucg_binary_search(r, ucg_spacing_mark_ranges, UCG_TABLE_LEN(ucg_spacing_mark_ranges)/2, 2); + if (p >= 0 && ucg_spacing_mark_ranges[p] <= r && r <= ucg_spacing_mark_ranges[p+1]) { + return true; + } + return false; +} + +// General_Category=Nonspacing_Mark +bool ucg_is_nonspacing_mark(int32_t r) { + intptr_t p = ucg_binary_search(r, ucg_nonspacing_mark_ranges, UCG_TABLE_LEN(ucg_nonspacing_mark_ranges)/2, 2); + if (p >= 0 && ucg_nonspacing_mark_ranges[p] <= r && r <= ucg_nonspacing_mark_ranges[p+1]) { + return true; + } + return false; +} + +// Extended_Pictographic +bool ucg_is_emoji_extended_pictographic(int32_t r) { + intptr_t p = ucg_binary_search(r, ucg_emoji_extended_pictographic_ranges, UCG_TABLE_LEN(ucg_emoji_extended_pictographic_ranges)/2, 2); + if (p >= 0 && ucg_emoji_extended_pictographic_ranges[p] <= r && r <= ucg_emoji_extended_pictographic_ranges[p+1]) { + return true; + } + return false; +} + +// Grapheme_Extend +bool ucg_is_grapheme_extend(int32_t r) { + intptr_t p = ucg_binary_search(r, ucg_grapheme_extend_ranges, UCG_TABLE_LEN(ucg_grapheme_extend_ranges)/2, 2); + if (p >= 0 && ucg_grapheme_extend_ranges[p] <= r && r <= ucg_grapheme_extend_ranges[p+1]) { + return true; + } + return false; +} + + +// Hangul_Syllable_Type=Leading_Jamo +bool ucg_is_hangul_syllable_leading(int32_t r) { + return (0x1100 <= r && r <= 0x115F) || (0xA960 <= r && r <= 0xA97C); +} + +// Hangul_Syllable_Type=Vowel_Jamo +bool ucg_is_hangul_syllable_vowel(int32_t r) { + return (0x1160 <= r && r <= 0x11A7) || (0xD7B0 <= r && r <= 0xD7C6); +} + +// Hangul_Syllable_Type=Trailing_Jamo +bool ucg_is_hangul_syllable_trailing(int32_t r) { + return (0x11A8 <= r && r <= 0x11FF) || (0xD7CB <= r && r <= 0xD7FB); +} + +// Hangul_Syllable_Type=LV_Syllable +bool ucg_is_hangul_syllable_lv(int32_t r) { + intptr_t p = ucg_binary_search(r, ucg_hangul_syllable_lv_singlets, UCG_TABLE_LEN(ucg_hangul_syllable_lv_singlets), 1); + if (p >= 0 && r == ucg_hangul_syllable_lv_singlets[p]) { + return true; + } + return false; +} + +// Hangul_Syllable_Type=LVT_Syllable +bool ucg_is_hangul_syllable_lvt(int32_t r) { + intptr_t p = ucg_binary_search(r, ucg_hangul_syllable_lvt_ranges, UCG_TABLE_LEN(ucg_hangul_syllable_lvt_ranges)/2, 2); + if (p >= 0 && ucg_hangul_syllable_lvt_ranges[p] <= r && r <= ucg_hangul_syllable_lvt_ranges[p+1]) { + return true; + } + return false; +} + + +// Indic_Syllabic_Category=Consonant_Preceding_Repha +bool ucg_is_indic_consonant_preceding_repha(int32_t r) { + switch (r) { + case 0x00D4E: + case 0x11941: + case 0x11D46: + case 0x11F02: + return true; + } + return false; +} + +// Indic_Syllabic_Category=Consonant_Prefixed +bool ucg_is_indic_consonant_prefixed(int32_t r) { + switch (r) { + case 0x1193F: + case 0x11A3A: + return true; + } + + if (0x111C2 <= r && r <= 0x111C3) { return true; } + if (0x11A84 <= r && r <= 0x11A89) { return true; } + + return false; +} + +// Indic_Conjunct_Break=Linker +bool ucg_is_indic_conjunct_break_linker(int32_t r) { + switch (r) { + case 0x094D: + case 0x09CD: + case 0x0ACD: + case 0x0B4D: + case 0x0C4D: + case 0x0D4D: + return true; + } + return false; +} + +// Indic_Conjunct_Break=Consonant +bool ucg_is_indic_conjunct_break_consonant(int32_t r) { + intptr_t p = ucg_binary_search(r, ucg_indic_conjunct_break_consonant_ranges, UCG_TABLE_LEN(ucg_indic_conjunct_break_consonant_ranges)/2, 2); + if (p >= 0 && ucg_indic_conjunct_break_consonant_ranges[p] <= r && r <= ucg_indic_conjunct_break_consonant_ranges[p+1]) { + return true; + } + return false; +} + +// Indic_Conjunct_Break=Extend +bool ucg_is_indic_conjunct_break_extend(int32_t r) { + intptr_t p = ucg_binary_search(r, ucg_indic_conjunct_break_extend_ranges, UCG_TABLE_LEN(ucg_indic_conjunct_break_extend_ranges)/2, 2); + if (p >= 0 && ucg_indic_conjunct_break_extend_ranges[p] <= r && r <= ucg_indic_conjunct_break_extend_ranges[p+1]) { + return true; + } + return false; +} + + +/* +``` +Indic_Syllabic_Category = Consonant_Preceding_Repha, or +Indic_Syllabic_Category = Consonant_Prefixed, or +Prepended_Concatenation_Mark = Yes +``` +*/ +bool ucg_is_gcb_prepend_class(int32_t r) { + return ucg_is_indic_consonant_preceding_repha(r) || ucg_is_indic_consonant_prefixed(r) || ucg_is_prepended_concatenation_mark(r); +} + +/* +``` +Grapheme_Extend = Yes, or +Emoji_Modifier = Yes + +This includes: +General_Category = Nonspacing_Mark +General_Category = Enclosing_Mark +U+200C ZERO WIDTH NON-JOINER + +plus a few General_Category = Spacing_Mark needed for canonical equivalence. +``` +*/ +bool ucg_is_gcb_extend_class(int32_t r) { + return ucg_is_grapheme_extend(r) || ucg_is_emoji_modifier(r); +} + +// Return values: +// +// - 2 if East_Asian_Width=F or W, or +// - 0 if non-printable / zero-width, or +// - 1 in all other cases. +// +int ucg_normalized_east_asian_width(int32_t r) { + if (ucg_is_control(r)) { + return 0; + } else if (r <= 0x10FF) { + // Easy early out for low runes. + return 1; + } + + switch (r) { + // This is a different interpretation of the BOM which occurs in the middle of text. + case 0xFEFF: /* ZERO_WIDTH_NO_BREAK_SPACE */ + case ZERO_WIDTH_SPACE: + case ZERO_WIDTH_NON_JOINER: + case ZERO_WIDTH_JOINER: + case WORD_JOINER: + return 0; + } + + intptr_t p = ucg_binary_search(r, ucg_normalized_east_asian_width_ranges, UCG_TABLE_LEN(ucg_normalized_east_asian_width_ranges)/3, 3); + if (p >= 0 && ucg_normalized_east_asian_width_ranges[p] <= r && r <= ucg_normalized_east_asian_width_ranges[p+1]) { + return (int)ucg_normalized_east_asian_width_ranges[p+2]; + } + return 1; +} + +// +// End of Unicode 15.1.0 block. +// + +enum grapheme_cluster_sequence { + None, + Indic, + Emoji, + Regional, +}; + +typedef struct { + ucg_grapheme* graphemes; + i32 rune_count; + i32 grapheme_count; + i32 width; + + int32_t last_rune; + bool last_rune_breaks_forward; + + i32 last_width; + i32 last_grapheme_count; + + bool bypass_next_rune; + + int regional_indicator_counter; + + enum grapheme_cluster_sequence current_sequence; + bool continue_sequence; +} ucg_decoder_state; + + +void _ucg_decode_grapheme_clusters_deferred_step( + gbAllocator allocator, + ucg_decoder_state* state, + i32 byte_index, + int32_t this_rune +) { + // "Break at the start and end of text, unless the text is empty." + // + // GB1: sot ÷ Any + // GB2: Any ÷ eot + if (state->rune_count == 0 && state->grapheme_count == 0) { + state->grapheme_count += 1; + } + + if (state->grapheme_count > state->last_grapheme_count) { + state->width += ucg_normalized_east_asian_width(this_rune); + + /* if (allocator != NULL) { */ + state->graphemes = (ucg_grapheme*)gb_resize(allocator, + state->graphemes, + sizeof(ucg_grapheme) * (state->grapheme_count), + sizeof(ucg_grapheme) * (1 + state->grapheme_count)); + + ucg_grapheme append = { + byte_index, + state->rune_count, + state->width - state->last_width, + }; + + state->graphemes[state->grapheme_count - 1] = append; + /* } */ + + state->last_grapheme_count = state->grapheme_count; + state->last_width = state->width; + } + + state->last_rune = this_rune; + state->rune_count += 1; + + if (!state->continue_sequence) { + state->current_sequence = None; + state->regional_indicator_counter = 0; + } + state->continue_sequence = false; +} + +int ucg_decode_grapheme_clusters( + gbAllocator allocator, + const uint8_t* str, + int str_len, + + ucg_grapheme** out_graphemes, + i32* out_rune_count, + i32* out_grapheme_count, + i32* out_width +) { + // The following procedure implements text segmentation by breaking on + // Grapheme Cluster Boundaries[1], using the values[2] and rules[3] from + // the Unicode® Standard Annex #29, entitled: + // + // UNICODE TEXT SEGMENTATION + // + // Version: Unicode 15.1.0 + // Date: 2023-08-16 + // Revision: 43 + // + // This procedure is conformant[4] to UAX29-C1-1, otherwise known as the + // extended, non-legacy ruleset. + // + // Please see the references for more information. + // + // + // [1]: https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries + // [2]: https://www.unicode.org/reports/tr29/#Default_Grapheme_Cluster_Table + // [3]: https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules + // [4]: https://www.unicode.org/reports/tr29/#Conformance + + // Additionally, this procedure takes into account Standard Annex #11, + // in order to estimate how visually wide the string will appear on a + // monospaced display. This can only ever be a rough guess, as this tends + // to be an implementation detail relating to which fonts are being used, + // how codepoints are interpreted and drawn, if codepoint sequences are + // interpreted correctly, and et cetera. + // + // For example, a program may not properly interpret an emoji modifier + // sequence and print the component glyphs instead of one whole glyph. + // + // See here for more information: https://www.unicode.org/reports/tr11/ + // + // NOTE: There is no explicit mention of what to do with zero-width spaces + // as far as grapheme cluster segmentation goes, therefore this + // implementation may count and return graphemes with a `width` of zero. + // + // Treat them as any other space. + + ucg_decoder_state state = {0}; + +#define UCG_DEFERRED_DECODE_STEP() (_ucg_decode_grapheme_clusters_deferred_step(allocator, &state, byte_index, this_rune)) + + for (i32 byte_index = 0, bytes_advanced = 0; byte_index < str_len; byte_index += bytes_advanced) { + int32_t this_rune = GB_RUNE_INVALID; + bytes_advanced = (i32)(utf8_decode(str+byte_index, str_len-byte_index, &this_rune)); + if (this_rune == GB_RUNE_INVALID || bytes_advanced == 0) { + // There was a Unicode parsing error; bail out. + if (out_graphemes != NULL) { *out_graphemes = state.graphemes; } + if (out_rune_count != NULL) { *out_rune_count = state.rune_count; } + if (out_grapheme_count != NULL) { *out_grapheme_count = state.grapheme_count; } + if (out_width != NULL) { *out_width = state.width; } + + // Return an error code. + return -1; + } + + // "Do not break between a CR and LF. Otherwise, break before and after controls." + // + // GB3: CR × LF + // GB4: (Control | CR | LF) ÷ + // GB5: ÷ (Control | CR | LF) + if (this_rune == '\n' && state.last_rune == '\r') { + state.last_rune_breaks_forward = false; + state.bypass_next_rune = false; + UCG_DEFERRED_DECODE_STEP(); continue; + } + + if (ucg_is_control(this_rune)) { + state.grapheme_count += 1; + state.last_rune_breaks_forward = true; + state.bypass_next_rune = true; + UCG_DEFERRED_DECODE_STEP(); continue; + } + + // (This check is for rules that work forwards, instead of backwards.) + if (state.bypass_next_rune) { + if (state.last_rune_breaks_forward) { + state.grapheme_count += 1; + state.last_rune_breaks_forward = false; + } + + state.bypass_next_rune = false; + UCG_DEFERRED_DECODE_STEP(); continue; + } + + // (Optimization 1: Prevent low runes from proceeding further.) + // + // * 0xA9 and 0xAE are in the Extended_Pictographic range, + // which is checked later in GB11. + if (this_rune != 0xA9 && this_rune != 0xAE && this_rune <= 0x2FF) { + state.grapheme_count += 1; + UCG_DEFERRED_DECODE_STEP(); continue; + } + + // (Optimization 2: Check if the rune is in the Hangul space before getting specific.) + if (0x1100 <= this_rune && this_rune <= 0xD7FB) { + // "Do not break Hangul syllable sequences." + // + // GB6: L × (L | V | LV | LVT) + // GB7: (LV | V) × (V | T) + // GB8: (LVT | T) × T + if (ucg_is_hangul_syllable_leading(this_rune) || + ucg_is_hangul_syllable_lv(this_rune) || + ucg_is_hangul_syllable_lvt(this_rune)) + { + if (!ucg_is_hangul_syllable_leading(state.last_rune)) { + state.grapheme_count += 1; + } + UCG_DEFERRED_DECODE_STEP(); continue; + } + + if (ucg_is_hangul_syllable_vowel(this_rune)) { + if (ucg_is_hangul_syllable_leading(state.last_rune) || + ucg_is_hangul_syllable_vowel(state.last_rune) || + ucg_is_hangul_syllable_lv(state.last_rune)) + { + UCG_DEFERRED_DECODE_STEP(); continue; + } + state.grapheme_count += 1; + UCG_DEFERRED_DECODE_STEP(); continue; + } + + if (ucg_is_hangul_syllable_trailing(this_rune)) { + if (ucg_is_hangul_syllable_trailing(state.last_rune) || + ucg_is_hangul_syllable_lvt(state.last_rune) || + ucg_is_hangul_syllable_lv(state.last_rune) || + ucg_is_hangul_syllable_vowel(state.last_rune)) + { + UCG_DEFERRED_DECODE_STEP(); continue; + } + state.grapheme_count += 1; + UCG_DEFERRED_DECODE_STEP(); continue; + } + } + + // "Do not break before extending characters or ZWJ." + // + // GB9: × (Extend | ZWJ) + if (this_rune == ZERO_WIDTH_JOINER) { + state.continue_sequence = true; + UCG_DEFERRED_DECODE_STEP(); continue; + } + + if (ucg_is_gcb_extend_class(this_rune)) { + // (Support for GB9c.) + if (state.current_sequence == Indic) { + if (ucg_is_indic_conjunct_break_extend(this_rune) && ( + ucg_is_indic_conjunct_break_linker(state.last_rune) || + ucg_is_indic_conjunct_break_consonant(state.last_rune) )) + { + state.continue_sequence = true; + UCG_DEFERRED_DECODE_STEP(); continue; + } + + if (ucg_is_indic_conjunct_break_linker(this_rune) && ( + ucg_is_indic_conjunct_break_linker(state.last_rune) || + ucg_is_indic_conjunct_break_extend(state.last_rune) || + ucg_is_indic_conjunct_break_consonant(state.last_rune) )) + { + state.continue_sequence = true; + UCG_DEFERRED_DECODE_STEP(); continue; + } + + UCG_DEFERRED_DECODE_STEP(); continue; + } + + // (Support for GB11.) + if (state.current_sequence == Emoji && ( + ucg_is_gcb_extend_class(state.last_rune) || + ucg_is_emoji_extended_pictographic(state.last_rune) )) + { + state.continue_sequence = true; + } + + UCG_DEFERRED_DECODE_STEP(); continue; + } + + // _The GB9a and GB9b rules only apply to extended grapheme clusters:_ + // "Do not break before SpacingMarks, or after Prepend characters." + // + // GB9a: × SpacingMark + // GB9b: Prepend × + if (ucg_is_spacing_mark(this_rune)) { + UCG_DEFERRED_DECODE_STEP(); continue; + } + + if (ucg_is_gcb_prepend_class(this_rune)) { + state.grapheme_count += 1; + state.bypass_next_rune = true; + UCG_DEFERRED_DECODE_STEP(); continue; + } + + // _The GB9c rule only applies to extended grapheme clusters:_ + // "Do not break within certain combinations with Indic_Conjunct_Break (InCB)=Linker." + // + // GB9c: \p{InCB=Consonant} [ \p{InCB=Extend} \p{InCB=Linker} ]* \p{InCB=Linker} [ \p{InCB=Extend} \p{InCB=Linker} ]* × \p{InCB=Consonant} + if (ucg_is_indic_conjunct_break_consonant(this_rune)) { + if (state.current_sequence == Indic) { + if (state.last_rune == ZERO_WIDTH_JOINER || + ucg_is_indic_conjunct_break_linker(state.last_rune)) + { + state.continue_sequence = true; + } else { + state.grapheme_count += 1; + } + } else { + state.grapheme_count += 1; + state.current_sequence = Indic; + state.continue_sequence = true; + } + UCG_DEFERRED_DECODE_STEP(); continue; + } + + if (ucg_is_indic_conjunct_break_extend(this_rune)) { + if (state.current_sequence == Indic) { + if (ucg_is_indic_conjunct_break_consonant(state.last_rune) || + ucg_is_indic_conjunct_break_linker(state.last_rune)) + { + state.continue_sequence = true; + } else { + state.grapheme_count += 1; + } + } + UCG_DEFERRED_DECODE_STEP(); continue; + } + + if (ucg_is_indic_conjunct_break_linker(this_rune)) { + if (state.current_sequence == Indic) { + if (ucg_is_indic_conjunct_break_extend(state.last_rune) || + ucg_is_indic_conjunct_break_linker(state.last_rune)) + { + state.continue_sequence = true; + } else { + state.grapheme_count += 1; + } + } + UCG_DEFERRED_DECODE_STEP(); continue; + } + + // + // (Curiously, there is no GB10.) + // + + // "Do not break within emoji modifier sequences or emoji zwj sequences." + // + // GB11: \p{Extended_Pictographic} Extend* ZWJ × \p{Extended_Pictographic} + if (ucg_is_emoji_extended_pictographic(this_rune)) { + if (state.current_sequence != Emoji || state.last_rune != ZERO_WIDTH_JOINER) { + state.grapheme_count += 1; + } + state.current_sequence = Emoji; + state.continue_sequence = true; + UCG_DEFERRED_DECODE_STEP(); continue; + } + + // "Do not break within emoji flag sequences. + // That is, do not break between regional indicator (RI) symbols + // if there is an odd number of RI characters before the break point." + // + // GB12: sot (RI RI)* RI × RI + // GB13: [^RI] (RI RI)* RI × RI + if (ucg_is_regional_indicator(this_rune)) { + if ((state.regional_indicator_counter & 1) == 0) { + state.grapheme_count += 1; + } + + state.current_sequence = Regional; + state.continue_sequence = true; + state.regional_indicator_counter += 1; + + UCG_DEFERRED_DECODE_STEP(); continue; + } + + // "Otherwise, break everywhere." + // + // GB999: Any ÷ Any + state.grapheme_count += 1; + UCG_DEFERRED_DECODE_STEP(); + } + +#undef UCG_DEFERRED_DECODE_STEP + + if (out_graphemes != NULL) { *out_graphemes = state.graphemes; } + if (out_rune_count != NULL) { *out_rune_count = state.rune_count; } + if (out_grapheme_count != NULL) { *out_grapheme_count = state.grapheme_count; } + if (out_width != NULL) { *out_width = state.width; } + + return 0; +} + +#undef UCG_TABLE_LEN +#undef ZERO_WIDTH_SPACE +#undef ZERO_WIDTH_NON_JOINER +#undef ZERO_WIDTH_JOINER +#undef WORD_JOINER diff --git a/src/ucg/ucg_tables.h b/src/ucg/ucg_tables.h new file mode 100644 index 000000000..a33f9f898 --- /dev/null +++ b/src/ucg/ucg_tables.h @@ -0,0 +1,2629 @@ +/* + * SPDX-FileCopyrightText: (c) 2024 Feoramund + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef _UCG_TABLES_INCLUDED +#define _UCG_TABLES_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +// +// The tables below are accurate as of Unicode 15.1.0. +// + +static const int32_t ucg_spacing_mark_ranges[] = { + 0x0903, 0x0903, + 0x093B, 0x093B, + 0x093E, 0x0940, + 0x0949, 0x094C, + 0x094E, 0x094F, + 0x0982, 0x0983, + 0x09BE, 0x09C0, + 0x09C7, 0x09C8, + 0x09CB, 0x09CC, + 0x09D7, 0x09D7, + 0x0A03, 0x0A03, + 0x0A3E, 0x0A40, + 0x0A83, 0x0A83, + 0x0ABE, 0x0AC0, + 0x0AC9, 0x0AC9, + 0x0ACB, 0x0ACC, + 0x0B02, 0x0B03, + 0x0B3E, 0x0B3E, + 0x0B40, 0x0B40, + 0x0B47, 0x0B48, + 0x0B4B, 0x0B4C, + 0x0B57, 0x0B57, + 0x0BBE, 0x0BBF, + 0x0BC1, 0x0BC2, + 0x0BC6, 0x0BC8, + 0x0BCA, 0x0BCC, + 0x0BD7, 0x0BD7, + 0x0C01, 0x0C03, + 0x0C41, 0x0C44, + 0x0C82, 0x0C83, + 0x0CBE, 0x0CBE, + 0x0CC0, 0x0CC4, + 0x0CC7, 0x0CC8, + 0x0CCA, 0x0CCB, + 0x0CD5, 0x0CD6, + 0x0CF3, 0x0CF3, + 0x0D02, 0x0D03, + 0x0D3E, 0x0D40, + 0x0D46, 0x0D48, + 0x0D4A, 0x0D4C, + 0x0D57, 0x0D57, + 0x0D82, 0x0D83, + 0x0DCF, 0x0DD1, + 0x0DD8, 0x0DDF, + 0x0DF2, 0x0DF3, + 0x0F3E, 0x0F3F, + 0x0F7F, 0x0F7F, + 0x102B, 0x102C, + 0x1031, 0x1031, + 0x1038, 0x1038, + 0x103B, 0x103C, + 0x1056, 0x1057, + 0x1062, 0x1064, + 0x1067, 0x106D, + 0x1083, 0x1084, + 0x1087, 0x108C, + 0x108F, 0x108F, + 0x109A, 0x109C, + 0x1715, 0x1715, + 0x1734, 0x1734, + 0x17B6, 0x17B6, + 0x17BE, 0x17C5, + 0x17C7, 0x17C8, + 0x1923, 0x1926, + 0x1929, 0x192B, + 0x1930, 0x1931, + 0x1933, 0x1938, + 0x1A19, 0x1A1A, + 0x1A55, 0x1A55, + 0x1A57, 0x1A57, + 0x1A61, 0x1A61, + 0x1A63, 0x1A64, + 0x1A6D, 0x1A72, + 0x1B04, 0x1B04, + 0x1B35, 0x1B35, + 0x1B3B, 0x1B3B, + 0x1B3D, 0x1B41, + 0x1B43, 0x1B44, + 0x1B82, 0x1B82, + 0x1BA1, 0x1BA1, + 0x1BA6, 0x1BA7, + 0x1BAA, 0x1BAA, + 0x1BE7, 0x1BE7, + 0x1BEA, 0x1BEC, + 0x1BEE, 0x1BEE, + 0x1BF2, 0x1BF3, + 0x1C24, 0x1C2B, + 0x1C34, 0x1C35, + 0x1CE1, 0x1CE1, + 0x1CF7, 0x1CF7, + 0x302E, 0x302F, + 0xA823, 0xA824, + 0xA827, 0xA827, + 0xA880, 0xA881, + 0xA8B4, 0xA8C3, + 0xA952, 0xA953, + 0xA983, 0xA983, + 0xA9B4, 0xA9B5, + 0xA9BA, 0xA9BB, + 0xA9BE, 0xA9C0, + 0xAA2F, 0xAA30, + 0xAA33, 0xAA34, + 0xAA4D, 0xAA4D, + 0xAA7B, 0xAA7B, + 0xAA7D, 0xAA7D, + 0xAAEB, 0xAAEB, + 0xAAEE, 0xAAEF, + 0xAAF5, 0xAAF5, + 0xABE3, 0xABE4, + 0xABE6, 0xABE7, + 0xABE9, 0xABEA, + 0xABEC, 0xABEC, + 0x11000, 0x11000, + 0x11002, 0x11002, + 0x11082, 0x11082, + 0x110B0, 0x110B2, + 0x110B7, 0x110B8, + 0x1112C, 0x1112C, + 0x11145, 0x11146, + 0x11182, 0x11182, + 0x111B3, 0x111B5, + 0x111BF, 0x111C0, + 0x111CE, 0x111CE, + 0x1122C, 0x1122E, + 0x11232, 0x11233, + 0x11235, 0x11235, + 0x112E0, 0x112E2, + 0x11302, 0x11303, + 0x1133E, 0x1133F, + 0x11341, 0x11344, + 0x11347, 0x11348, + 0x1134B, 0x1134D, + 0x11357, 0x11357, + 0x11362, 0x11363, + 0x11435, 0x11437, + 0x11440, 0x11441, + 0x11445, 0x11445, + 0x114B0, 0x114B2, + 0x114B9, 0x114B9, + 0x114BB, 0x114BE, + 0x114C1, 0x114C1, + 0x115AF, 0x115B1, + 0x115B8, 0x115BB, + 0x115BE, 0x115BE, + 0x11630, 0x11632, + 0x1163B, 0x1163C, + 0x1163E, 0x1163E, + 0x116AC, 0x116AC, + 0x116AE, 0x116AF, + 0x116B6, 0x116B6, + 0x11720, 0x11721, + 0x11726, 0x11726, + 0x1182C, 0x1182E, + 0x11838, 0x11838, + 0x11930, 0x11935, + 0x11937, 0x11938, + 0x1193D, 0x1193D, + 0x11940, 0x11940, + 0x11942, 0x11942, + 0x119D1, 0x119D3, + 0x119DC, 0x119DF, + 0x119E4, 0x119E4, + 0x11A39, 0x11A39, + 0x11A57, 0x11A58, + 0x11A97, 0x11A97, + 0x11C2F, 0x11C2F, + 0x11C3E, 0x11C3E, + 0x11CA9, 0x11CA9, + 0x11CB1, 0x11CB1, + 0x11CB4, 0x11CB4, + 0x11D8A, 0x11D8E, + 0x11D93, 0x11D94, + 0x11D96, 0x11D96, + 0x11EF5, 0x11EF6, + 0x11F03, 0x11F03, + 0x11F34, 0x11F35, + 0x11F3E, 0x11F3F, + 0x11F41, 0x11F41, + 0x16F51, 0x16F87, + 0x16FF0, 0x16FF1, + 0x1D165, 0x1D166, + 0x1D16D, 0x1D172, +}; + +static const int32_t ucg_nonspacing_mark_ranges[] = { + 0x0300, 0x036F, + 0x0483, 0x0487, + 0x0591, 0x05BD, + 0x05BF, 0x05BF, + 0x05C1, 0x05C2, + 0x05C4, 0x05C5, + 0x05C7, 0x05C7, + 0x0610, 0x061A, + 0x064B, 0x065F, + 0x0670, 0x0670, + 0x06D6, 0x06DC, + 0x06DF, 0x06E4, + 0x06E7, 0x06E8, + 0x06EA, 0x06ED, + 0x0711, 0x0711, + 0x0730, 0x074A, + 0x07A6, 0x07B0, + 0x07EB, 0x07F3, + 0x07FD, 0x07FD, + 0x0816, 0x0819, + 0x081B, 0x0823, + 0x0825, 0x0827, + 0x0829, 0x082D, + 0x0859, 0x085B, + 0x0898, 0x089F, + 0x08CA, 0x08E1, + 0x08E3, 0x0902, + 0x093A, 0x093A, + 0x093C, 0x093C, + 0x0941, 0x0948, + 0x094D, 0x094D, + 0x0951, 0x0957, + 0x0962, 0x0963, + 0x0981, 0x0981, + 0x09BC, 0x09BC, + 0x09C1, 0x09C4, + 0x09CD, 0x09CD, + 0x09E2, 0x09E3, + 0x09FE, 0x09FE, + 0x0A01, 0x0A02, + 0x0A3C, 0x0A3C, + 0x0A41, 0x0A42, + 0x0A47, 0x0A48, + 0x0A4B, 0x0A4D, + 0x0A51, 0x0A51, + 0x0A70, 0x0A71, + 0x0A75, 0x0A75, + 0x0A81, 0x0A82, + 0x0ABC, 0x0ABC, + 0x0AC1, 0x0AC5, + 0x0AC7, 0x0AC8, + 0x0ACD, 0x0ACD, + 0x0AE2, 0x0AE3, + 0x0AFA, 0x0AFF, + 0x0B01, 0x0B01, + 0x0B3C, 0x0B3C, + 0x0B3F, 0x0B3F, + 0x0B41, 0x0B44, + 0x0B4D, 0x0B4D, + 0x0B55, 0x0B56, + 0x0B62, 0x0B63, + 0x0B82, 0x0B82, + 0x0BC0, 0x0BC0, + 0x0BCD, 0x0BCD, + 0x0C00, 0x0C00, + 0x0C04, 0x0C04, + 0x0C3C, 0x0C3C, + 0x0C3E, 0x0C40, + 0x0C46, 0x0C48, + 0x0C4A, 0x0C4D, + 0x0C55, 0x0C56, + 0x0C62, 0x0C63, + 0x0C81, 0x0C81, + 0x0CBC, 0x0CBC, + 0x0CBF, 0x0CBF, + 0x0CC6, 0x0CC6, + 0x0CCC, 0x0CCD, + 0x0CE2, 0x0CE3, + 0x0D00, 0x0D01, + 0x0D3B, 0x0D3C, + 0x0D41, 0x0D44, + 0x0D4D, 0x0D4D, + 0x0D62, 0x0D63, + 0x0D81, 0x0D81, + 0x0DCA, 0x0DCA, + 0x0DD2, 0x0DD4, + 0x0DD6, 0x0DD6, + 0x0E31, 0x0E31, + 0x0E34, 0x0E3A, + 0x0E47, 0x0E4E, + 0x0EB1, 0x0EB1, + 0x0EB4, 0x0EBC, + 0x0EC8, 0x0ECE, + 0x0F18, 0x0F19, + 0x0F35, 0x0F35, + 0x0F37, 0x0F37, + 0x0F39, 0x0F39, + 0x0F71, 0x0F7E, + 0x0F80, 0x0F84, + 0x0F86, 0x0F87, + 0x0F8D, 0x0F97, + 0x0F99, 0x0FBC, + 0x0FC6, 0x0FC6, + 0x102D, 0x1030, + 0x1032, 0x1037, + 0x1039, 0x103A, + 0x103D, 0x103E, + 0x1058, 0x1059, + 0x105E, 0x1060, + 0x1071, 0x1074, + 0x1082, 0x1082, + 0x1085, 0x1086, + 0x108D, 0x108D, + 0x109D, 0x109D, + 0x135D, 0x135F, + 0x1712, 0x1714, + 0x1732, 0x1733, + 0x1752, 0x1753, + 0x1772, 0x1773, + 0x17B4, 0x17B5, + 0x17B7, 0x17BD, + 0x17C6, 0x17C6, + 0x17C9, 0x17D3, + 0x17DD, 0x17DD, + 0x180B, 0x180D, + 0x180F, 0x180F, + 0x1885, 0x1886, + 0x18A9, 0x18A9, + 0x1920, 0x1922, + 0x1927, 0x1928, + 0x1932, 0x1932, + 0x1939, 0x193B, + 0x1A17, 0x1A18, + 0x1A1B, 0x1A1B, + 0x1A56, 0x1A56, + 0x1A58, 0x1A5E, + 0x1A60, 0x1A60, + 0x1A62, 0x1A62, + 0x1A65, 0x1A6C, + 0x1A73, 0x1A7C, + 0x1A7F, 0x1A7F, + 0x1AB0, 0x1ABD, + 0x1ABF, 0x1ACE, + 0x1B00, 0x1B03, + 0x1B34, 0x1B34, + 0x1B36, 0x1B3A, + 0x1B3C, 0x1B3C, + 0x1B42, 0x1B42, + 0x1B6B, 0x1B73, + 0x1B80, 0x1B81, + 0x1BA2, 0x1BA5, + 0x1BA8, 0x1BA9, + 0x1BAB, 0x1BAD, + 0x1BE6, 0x1BE6, + 0x1BE8, 0x1BE9, + 0x1BED, 0x1BED, + 0x1BEF, 0x1BF1, + 0x1C2C, 0x1C33, + 0x1C36, 0x1C37, + 0x1CD0, 0x1CD2, + 0x1CD4, 0x1CE0, + 0x1CE2, 0x1CE8, + 0x1CED, 0x1CED, + 0x1CF4, 0x1CF4, + 0x1CF8, 0x1CF9, + 0x1DC0, 0x1DFF, + 0x20D0, 0x20DC, + 0x20E1, 0x20E1, + 0x20E5, 0x20F0, + 0x2CEF, 0x2CF1, + 0x2D7F, 0x2D7F, + 0x2DE0, 0x2DFF, + 0x302A, 0x302D, + 0x3099, 0x309A, + 0xA66F, 0xA66F, + 0xA674, 0xA67D, + 0xA69E, 0xA69F, + 0xA6F0, 0xA6F1, + 0xA802, 0xA802, + 0xA806, 0xA806, + 0xA80B, 0xA80B, + 0xA825, 0xA826, + 0xA82C, 0xA82C, + 0xA8C4, 0xA8C5, + 0xA8E0, 0xA8F1, + 0xA8FF, 0xA8FF, + 0xA926, 0xA92D, + 0xA947, 0xA951, + 0xA980, 0xA982, + 0xA9B3, 0xA9B3, + 0xA9B6, 0xA9B9, + 0xA9BC, 0xA9BD, + 0xA9E5, 0xA9E5, + 0xAA29, 0xAA2E, + 0xAA31, 0xAA32, + 0xAA35, 0xAA36, + 0xAA43, 0xAA43, + 0xAA4C, 0xAA4C, + 0xAA7C, 0xAA7C, + 0xAAB0, 0xAAB0, + 0xAAB2, 0xAAB4, + 0xAAB7, 0xAAB8, + 0xAABE, 0xAABF, + 0xAAC1, 0xAAC1, + 0xAAEC, 0xAAED, + 0xAAF6, 0xAAF6, + 0xABE5, 0xABE5, + 0xABE8, 0xABE8, + 0xABED, 0xABED, + 0xFB1E, 0xFB1E, + 0xFE00, 0xFE0F, + 0xFE20, 0xFE2F, + 0x101FD, 0x101FD, + 0x102E0, 0x102E0, + 0x10376, 0x1037A, + 0x10A01, 0x10A03, + 0x10A05, 0x10A06, + 0x10A0C, 0x10A0F, + 0x10A38, 0x10A3A, + 0x10A3F, 0x10A3F, + 0x10AE5, 0x10AE6, + 0x10D24, 0x10D27, + 0x10EAB, 0x10EAC, + 0x10EFD, 0x10EFF, + 0x10F46, 0x10F50, + 0x10F82, 0x10F85, + 0x11001, 0x11001, + 0x11038, 0x11046, + 0x11070, 0x11070, + 0x11073, 0x11074, + 0x1107F, 0x11081, + 0x110B3, 0x110B6, + 0x110B9, 0x110BA, + 0x110C2, 0x110C2, + 0x11100, 0x11102, + 0x11127, 0x1112B, + 0x1112D, 0x11134, + 0x11173, 0x11173, + 0x11180, 0x11181, + 0x111B6, 0x111BE, + 0x111C9, 0x111CC, + 0x111CF, 0x111CF, + 0x1122F, 0x11231, + 0x11234, 0x11234, + 0x11236, 0x11237, + 0x1123E, 0x1123E, + 0x11241, 0x11241, + 0x112DF, 0x112DF, + 0x112E3, 0x112EA, + 0x11300, 0x11301, + 0x1133B, 0x1133C, + 0x11340, 0x11340, + 0x11366, 0x1136C, + 0x11370, 0x11374, + 0x11438, 0x1143F, + 0x11442, 0x11444, + 0x11446, 0x11446, + 0x1145E, 0x1145E, + 0x114B3, 0x114B8, + 0x114BA, 0x114BA, + 0x114BF, 0x114C0, + 0x114C2, 0x114C3, + 0x115B2, 0x115B5, + 0x115BC, 0x115BD, + 0x115BF, 0x115C0, + 0x115DC, 0x115DD, + 0x11633, 0x1163A, + 0x1163D, 0x1163D, + 0x1163F, 0x11640, + 0x116AB, 0x116AB, + 0x116AD, 0x116AD, + 0x116B0, 0x116B5, + 0x116B7, 0x116B7, + 0x1171D, 0x1171F, + 0x11722, 0x11725, + 0x11727, 0x1172B, + 0x1182F, 0x11837, + 0x11839, 0x1183A, + 0x1193B, 0x1193C, + 0x1193E, 0x1193E, + 0x11943, 0x11943, + 0x119D4, 0x119D7, + 0x119DA, 0x119DB, + 0x119E0, 0x119E0, + 0x11A01, 0x11A0A, + 0x11A33, 0x11A38, + 0x11A3B, 0x11A3E, + 0x11A47, 0x11A47, + 0x11A51, 0x11A56, + 0x11A59, 0x11A5B, + 0x11A8A, 0x11A96, + 0x11A98, 0x11A99, + 0x11C30, 0x11C36, + 0x11C38, 0x11C3D, + 0x11C3F, 0x11C3F, + 0x11C92, 0x11CA7, + 0x11CAA, 0x11CB0, + 0x11CB2, 0x11CB3, + 0x11CB5, 0x11CB6, + 0x11D31, 0x11D36, + 0x11D3A, 0x11D3A, + 0x11D3C, 0x11D3D, + 0x11D3F, 0x11D45, + 0x11D47, 0x11D47, + 0x11D90, 0x11D91, + 0x11D95, 0x11D95, + 0x11D97, 0x11D97, + 0x11EF3, 0x11EF4, + 0x11F00, 0x11F01, + 0x11F36, 0x11F3A, + 0x11F40, 0x11F40, + 0x11F42, 0x11F42, + 0x13440, 0x13440, + 0x13447, 0x13455, + 0x16AF0, 0x16AF4, + 0x16B30, 0x16B36, + 0x16F4F, 0x16F4F, + 0x16F8F, 0x16F92, + 0x16FE4, 0x16FE4, + 0x1BC9D, 0x1BC9E, + 0x1CF00, 0x1CF2D, + 0x1CF30, 0x1CF46, + 0x1D167, 0x1D169, + 0x1D17B, 0x1D182, + 0x1D185, 0x1D18B, + 0x1D1AA, 0x1D1AD, + 0x1D242, 0x1D244, + 0x1DA00, 0x1DA36, + 0x1DA3B, 0x1DA6C, + 0x1DA75, 0x1DA75, + 0x1DA84, 0x1DA84, + 0x1DA9B, 0x1DA9F, + 0x1DAA1, 0x1DAAF, + 0x1E000, 0x1E006, + 0x1E008, 0x1E018, + 0x1E01B, 0x1E021, + 0x1E023, 0x1E024, + 0x1E026, 0x1E02A, + 0x1E08F, 0x1E08F, + 0x1E130, 0x1E136, + 0x1E2AE, 0x1E2AE, + 0x1E2EC, 0x1E2EF, + 0x1E4EC, 0x1E4EF, + 0x1E8D0, 0x1E8D6, + 0x1E944, 0x1E94A, + 0xE0100, 0xE01EF, +}; + +static const int32_t ucg_emoji_extended_pictographic_ranges[] = { + 0x00A9, 0x00A9, + 0x00AE, 0x00AE, + 0x203C, 0x203C, + 0x2049, 0x2049, + 0x2122, 0x2122, + 0x2139, 0x2139, + 0x2194, 0x2199, + 0x21A9, 0x21AA, + 0x231A, 0x231B, + 0x2328, 0x2328, + 0x2388, 0x2388, + 0x23CF, 0x23CF, + 0x23E9, 0x23EC, + 0x23ED, 0x23EE, + 0x23EF, 0x23EF, + 0x23F0, 0x23F0, + 0x23F1, 0x23F2, + 0x23F3, 0x23F3, + 0x23F8, 0x23FA, + 0x24C2, 0x24C2, + 0x25AA, 0x25AB, + 0x25B6, 0x25B6, + 0x25C0, 0x25C0, + 0x25FB, 0x25FE, + 0x2600, 0x2601, + 0x2602, 0x2603, + 0x2604, 0x2604, + 0x2605, 0x2605, + 0x2607, 0x260D, + 0x260E, 0x260E, + 0x260F, 0x2610, + 0x2611, 0x2611, + 0x2612, 0x2612, + 0x2614, 0x2615, + 0x2616, 0x2617, + 0x2618, 0x2618, + 0x2619, 0x261C, + 0x261D, 0x261D, + 0x261E, 0x261F, + 0x2620, 0x2620, + 0x2621, 0x2621, + 0x2622, 0x2623, + 0x2624, 0x2625, + 0x2626, 0x2626, + 0x2627, 0x2629, + 0x262A, 0x262A, + 0x262B, 0x262D, + 0x262E, 0x262E, + 0x262F, 0x262F, + 0x2630, 0x2637, + 0x2638, 0x2639, + 0x263A, 0x263A, + 0x263B, 0x263F, + 0x2640, 0x2640, + 0x2641, 0x2641, + 0x2642, 0x2642, + 0x2643, 0x2647, + 0x2648, 0x2653, + 0x2654, 0x265E, + 0x265F, 0x265F, + 0x2660, 0x2660, + 0x2661, 0x2662, + 0x2663, 0x2663, + 0x2664, 0x2664, + 0x2665, 0x2666, + 0x2667, 0x2667, + 0x2668, 0x2668, + 0x2669, 0x267A, + 0x267B, 0x267B, + 0x267C, 0x267D, + 0x267E, 0x267E, + 0x267F, 0x267F, + 0x2680, 0x2685, + 0x2690, 0x2691, + 0x2692, 0x2692, + 0x2693, 0x2693, + 0x2694, 0x2694, + 0x2695, 0x2695, + 0x2696, 0x2697, + 0x2698, 0x2698, + 0x2699, 0x2699, + 0x269A, 0x269A, + 0x269B, 0x269C, + 0x269D, 0x269F, + 0x26A0, 0x26A1, + 0x26A2, 0x26A6, + 0x26A7, 0x26A7, + 0x26A8, 0x26A9, + 0x26AA, 0x26AB, + 0x26AC, 0x26AF, + 0x26B0, 0x26B1, + 0x26B2, 0x26BC, + 0x26BD, 0x26BE, + 0x26BF, 0x26C3, + 0x26C4, 0x26C5, + 0x26C6, 0x26C7, + 0x26C8, 0x26C8, + 0x26C9, 0x26CD, + 0x26CE, 0x26CE, + 0x26CF, 0x26CF, + 0x26D0, 0x26D0, + 0x26D1, 0x26D1, + 0x26D2, 0x26D2, + 0x26D3, 0x26D3, + 0x26D4, 0x26D4, + 0x26D5, 0x26E8, + 0x26E9, 0x26E9, + 0x26EA, 0x26EA, + 0x26EB, 0x26EF, + 0x26F0, 0x26F1, + 0x26F2, 0x26F3, + 0x26F4, 0x26F4, + 0x26F5, 0x26F5, + 0x26F6, 0x26F6, + 0x26F7, 0x26F9, + 0x26FA, 0x26FA, + 0x26FB, 0x26FC, + 0x26FD, 0x26FD, + 0x26FE, 0x2701, + 0x2702, 0x2702, + 0x2703, 0x2704, + 0x2705, 0x2705, + 0x2708, 0x270C, + 0x270D, 0x270D, + 0x270E, 0x270E, + 0x270F, 0x270F, + 0x2710, 0x2711, + 0x2712, 0x2712, + 0x2714, 0x2714, + 0x2716, 0x2716, + 0x271D, 0x271D, + 0x2721, 0x2721, + 0x2728, 0x2728, + 0x2733, 0x2734, + 0x2744, 0x2744, + 0x2747, 0x2747, + 0x274C, 0x274C, + 0x274E, 0x274E, + 0x2753, 0x2755, + 0x2757, 0x2757, + 0x2763, 0x2763, + 0x2764, 0x2764, + 0x2765, 0x2767, + 0x2795, 0x2797, + 0x27A1, 0x27A1, + 0x27B0, 0x27B0, + 0x27BF, 0x27BF, + 0x2934, 0x2935, + 0x2B05, 0x2B07, + 0x2B1B, 0x2B1C, + 0x2B50, 0x2B50, + 0x2B55, 0x2B55, + 0x3030, 0x3030, + 0x303D, 0x303D, + 0x3297, 0x3297, + 0x3299, 0x3299, + 0x1F000, 0x1F003, + 0x1F004, 0x1F004, + 0x1F005, 0x1F0CE, + 0x1F0CF, 0x1F0CF, + 0x1F0D0, 0x1F0FF, + 0x1F10D, 0x1F10F, + 0x1F12F, 0x1F12F, + 0x1F16C, 0x1F16F, + 0x1F170, 0x1F171, + 0x1F17E, 0x1F17F, + 0x1F18E, 0x1F18E, + 0x1F191, 0x1F19A, + 0x1F1AD, 0x1F1E5, + 0x1F201, 0x1F202, + 0x1F203, 0x1F20F, + 0x1F21A, 0x1F21A, + 0x1F22F, 0x1F22F, + 0x1F232, 0x1F23A, + 0x1F23C, 0x1F23F, + 0x1F249, 0x1F24F, + 0x1F250, 0x1F251, + 0x1F252, 0x1F2FF, + 0x1F300, 0x1F30C, + 0x1F30D, 0x1F30E, + 0x1F30F, 0x1F30F, + 0x1F310, 0x1F310, + 0x1F311, 0x1F311, + 0x1F312, 0x1F312, + 0x1F313, 0x1F315, + 0x1F316, 0x1F318, + 0x1F319, 0x1F319, + 0x1F31A, 0x1F31A, + 0x1F31B, 0x1F31B, + 0x1F31C, 0x1F31C, + 0x1F31D, 0x1F31E, + 0x1F31F, 0x1F320, + 0x1F321, 0x1F321, + 0x1F322, 0x1F323, + 0x1F324, 0x1F32C, + 0x1F32D, 0x1F32F, + 0x1F330, 0x1F331, + 0x1F332, 0x1F333, + 0x1F334, 0x1F335, + 0x1F336, 0x1F336, + 0x1F337, 0x1F34A, + 0x1F34B, 0x1F34B, + 0x1F34C, 0x1F34F, + 0x1F350, 0x1F350, + 0x1F351, 0x1F37B, + 0x1F37C, 0x1F37C, + 0x1F37D, 0x1F37D, + 0x1F37E, 0x1F37F, + 0x1F380, 0x1F393, + 0x1F394, 0x1F395, + 0x1F396, 0x1F397, + 0x1F398, 0x1F398, + 0x1F399, 0x1F39B, + 0x1F39C, 0x1F39D, + 0x1F39E, 0x1F39F, + 0x1F3A0, 0x1F3C4, + 0x1F3C5, 0x1F3C5, + 0x1F3C6, 0x1F3C6, + 0x1F3C7, 0x1F3C7, + 0x1F3C8, 0x1F3C8, + 0x1F3C9, 0x1F3C9, + 0x1F3CA, 0x1F3CA, + 0x1F3CB, 0x1F3CE, + 0x1F3CF, 0x1F3D3, + 0x1F3D4, 0x1F3DF, + 0x1F3E0, 0x1F3E3, + 0x1F3E4, 0x1F3E4, + 0x1F3E5, 0x1F3F0, + 0x1F3F1, 0x1F3F2, + 0x1F3F3, 0x1F3F3, + 0x1F3F4, 0x1F3F4, + 0x1F3F5, 0x1F3F5, + 0x1F3F6, 0x1F3F6, + 0x1F3F7, 0x1F3F7, + 0x1F3F8, 0x1F3FA, + 0x1F400, 0x1F407, + 0x1F408, 0x1F408, + 0x1F409, 0x1F40B, + 0x1F40C, 0x1F40E, + 0x1F40F, 0x1F410, + 0x1F411, 0x1F412, + 0x1F413, 0x1F413, + 0x1F414, 0x1F414, + 0x1F415, 0x1F415, + 0x1F416, 0x1F416, + 0x1F417, 0x1F429, + 0x1F42A, 0x1F42A, + 0x1F42B, 0x1F43E, + 0x1F43F, 0x1F43F, + 0x1F440, 0x1F440, + 0x1F441, 0x1F441, + 0x1F442, 0x1F464, + 0x1F465, 0x1F465, + 0x1F466, 0x1F46B, + 0x1F46C, 0x1F46D, + 0x1F46E, 0x1F4AC, + 0x1F4AD, 0x1F4AD, + 0x1F4AE, 0x1F4B5, + 0x1F4B6, 0x1F4B7, + 0x1F4B8, 0x1F4EB, + 0x1F4EC, 0x1F4ED, + 0x1F4EE, 0x1F4EE, + 0x1F4EF, 0x1F4EF, + 0x1F4F0, 0x1F4F4, + 0x1F4F5, 0x1F4F5, + 0x1F4F6, 0x1F4F7, + 0x1F4F8, 0x1F4F8, + 0x1F4F9, 0x1F4FC, + 0x1F4FD, 0x1F4FD, + 0x1F4FE, 0x1F4FE, + 0x1F4FF, 0x1F502, + 0x1F503, 0x1F503, + 0x1F504, 0x1F507, + 0x1F508, 0x1F508, + 0x1F509, 0x1F509, + 0x1F50A, 0x1F514, + 0x1F515, 0x1F515, + 0x1F516, 0x1F52B, + 0x1F52C, 0x1F52D, + 0x1F52E, 0x1F53D, + 0x1F546, 0x1F548, + 0x1F549, 0x1F54A, + 0x1F54B, 0x1F54E, + 0x1F54F, 0x1F54F, + 0x1F550, 0x1F55B, + 0x1F55C, 0x1F567, + 0x1F568, 0x1F56E, + 0x1F56F, 0x1F570, + 0x1F571, 0x1F572, + 0x1F573, 0x1F579, + 0x1F57A, 0x1F57A, + 0x1F57B, 0x1F586, + 0x1F587, 0x1F587, + 0x1F588, 0x1F589, + 0x1F58A, 0x1F58D, + 0x1F58E, 0x1F58F, + 0x1F590, 0x1F590, + 0x1F591, 0x1F594, + 0x1F595, 0x1F596, + 0x1F597, 0x1F5A3, + 0x1F5A4, 0x1F5A4, + 0x1F5A5, 0x1F5A5, + 0x1F5A6, 0x1F5A7, + 0x1F5A8, 0x1F5A8, + 0x1F5A9, 0x1F5B0, + 0x1F5B1, 0x1F5B2, + 0x1F5B3, 0x1F5BB, + 0x1F5BC, 0x1F5BC, + 0x1F5BD, 0x1F5C1, + 0x1F5C2, 0x1F5C4, + 0x1F5C5, 0x1F5D0, + 0x1F5D1, 0x1F5D3, + 0x1F5D4, 0x1F5DB, + 0x1F5DC, 0x1F5DE, + 0x1F5DF, 0x1F5E0, + 0x1F5E1, 0x1F5E1, + 0x1F5E2, 0x1F5E2, + 0x1F5E3, 0x1F5E3, + 0x1F5E4, 0x1F5E7, + 0x1F5E8, 0x1F5E8, + 0x1F5E9, 0x1F5EE, + 0x1F5EF, 0x1F5EF, + 0x1F5F0, 0x1F5F2, + 0x1F5F3, 0x1F5F3, + 0x1F5F4, 0x1F5F9, + 0x1F5FA, 0x1F5FA, + 0x1F5FB, 0x1F5FF, + 0x1F600, 0x1F600, + 0x1F601, 0x1F606, + 0x1F607, 0x1F608, + 0x1F609, 0x1F60D, + 0x1F60E, 0x1F60E, + 0x1F60F, 0x1F60F, + 0x1F610, 0x1F610, + 0x1F611, 0x1F611, + 0x1F612, 0x1F614, + 0x1F615, 0x1F615, + 0x1F616, 0x1F616, + 0x1F617, 0x1F617, + 0x1F618, 0x1F618, + 0x1F619, 0x1F619, + 0x1F61A, 0x1F61A, + 0x1F61B, 0x1F61B, + 0x1F61C, 0x1F61E, + 0x1F61F, 0x1F61F, + 0x1F620, 0x1F625, + 0x1F626, 0x1F627, + 0x1F628, 0x1F62B, + 0x1F62C, 0x1F62C, + 0x1F62D, 0x1F62D, + 0x1F62E, 0x1F62F, + 0x1F630, 0x1F633, + 0x1F634, 0x1F634, + 0x1F635, 0x1F635, + 0x1F636, 0x1F636, + 0x1F637, 0x1F640, + 0x1F641, 0x1F644, + 0x1F645, 0x1F64F, + 0x1F680, 0x1F680, + 0x1F681, 0x1F682, + 0x1F683, 0x1F685, + 0x1F686, 0x1F686, + 0x1F687, 0x1F687, + 0x1F688, 0x1F688, + 0x1F689, 0x1F689, + 0x1F68A, 0x1F68B, + 0x1F68C, 0x1F68C, + 0x1F68D, 0x1F68D, + 0x1F68E, 0x1F68E, + 0x1F68F, 0x1F68F, + 0x1F690, 0x1F690, + 0x1F691, 0x1F693, + 0x1F694, 0x1F694, + 0x1F695, 0x1F695, + 0x1F696, 0x1F696, + 0x1F697, 0x1F697, + 0x1F698, 0x1F698, + 0x1F699, 0x1F69A, + 0x1F69B, 0x1F6A1, + 0x1F6A2, 0x1F6A2, + 0x1F6A3, 0x1F6A3, + 0x1F6A4, 0x1F6A5, + 0x1F6A6, 0x1F6A6, + 0x1F6A7, 0x1F6AD, + 0x1F6AE, 0x1F6B1, + 0x1F6B2, 0x1F6B2, + 0x1F6B3, 0x1F6B5, + 0x1F6B6, 0x1F6B6, + 0x1F6B7, 0x1F6B8, + 0x1F6B9, 0x1F6BE, + 0x1F6BF, 0x1F6BF, + 0x1F6C0, 0x1F6C0, + 0x1F6C1, 0x1F6C5, + 0x1F6C6, 0x1F6CA, + 0x1F6CB, 0x1F6CB, + 0x1F6CC, 0x1F6CC, + 0x1F6CD, 0x1F6CF, + 0x1F6D0, 0x1F6D0, + 0x1F6D1, 0x1F6D2, + 0x1F6D3, 0x1F6D4, + 0x1F6D5, 0x1F6D5, + 0x1F6D6, 0x1F6D7, + 0x1F6D8, 0x1F6DB, + 0x1F6DC, 0x1F6DC, + 0x1F6DD, 0x1F6DF, + 0x1F6E0, 0x1F6E5, + 0x1F6E6, 0x1F6E8, + 0x1F6E9, 0x1F6E9, + 0x1F6EA, 0x1F6EA, + 0x1F6EB, 0x1F6EC, + 0x1F6ED, 0x1F6EF, + 0x1F6F0, 0x1F6F0, + 0x1F6F1, 0x1F6F2, + 0x1F6F3, 0x1F6F3, + 0x1F6F4, 0x1F6F6, + 0x1F6F7, 0x1F6F8, + 0x1F6F9, 0x1F6F9, + 0x1F6FA, 0x1F6FA, + 0x1F6FB, 0x1F6FC, + 0x1F6FD, 0x1F6FF, + 0x1F774, 0x1F77F, + 0x1F7D5, 0x1F7DF, + 0x1F7E0, 0x1F7EB, + 0x1F7EC, 0x1F7EF, + 0x1F7F0, 0x1F7F0, + 0x1F7F1, 0x1F7FF, + 0x1F80C, 0x1F80F, + 0x1F848, 0x1F84F, + 0x1F85A, 0x1F85F, + 0x1F888, 0x1F88F, + 0x1F8AE, 0x1F8FF, + 0x1F90C, 0x1F90C, + 0x1F90D, 0x1F90F, + 0x1F910, 0x1F918, + 0x1F919, 0x1F91E, + 0x1F91F, 0x1F91F, + 0x1F920, 0x1F927, + 0x1F928, 0x1F92F, + 0x1F930, 0x1F930, + 0x1F931, 0x1F932, + 0x1F933, 0x1F93A, + 0x1F93C, 0x1F93E, + 0x1F93F, 0x1F93F, + 0x1F940, 0x1F945, + 0x1F947, 0x1F94B, + 0x1F94C, 0x1F94C, + 0x1F94D, 0x1F94F, + 0x1F950, 0x1F95E, + 0x1F95F, 0x1F96B, + 0x1F96C, 0x1F970, + 0x1F971, 0x1F971, + 0x1F972, 0x1F972, + 0x1F973, 0x1F976, + 0x1F977, 0x1F978, + 0x1F979, 0x1F979, + 0x1F97A, 0x1F97A, + 0x1F97B, 0x1F97B, + 0x1F97C, 0x1F97F, + 0x1F980, 0x1F984, + 0x1F985, 0x1F991, + 0x1F992, 0x1F997, + 0x1F998, 0x1F9A2, + 0x1F9A3, 0x1F9A4, + 0x1F9A5, 0x1F9AA, + 0x1F9AB, 0x1F9AD, + 0x1F9AE, 0x1F9AF, + 0x1F9B0, 0x1F9B9, + 0x1F9BA, 0x1F9BF, + 0x1F9C0, 0x1F9C0, + 0x1F9C1, 0x1F9C2, + 0x1F9C3, 0x1F9CA, + 0x1F9CB, 0x1F9CB, + 0x1F9CC, 0x1F9CC, + 0x1F9CD, 0x1F9CF, + 0x1F9D0, 0x1F9E6, + 0x1F9E7, 0x1F9FF, + 0x1FA00, 0x1FA6F, + 0x1FA70, 0x1FA73, + 0x1FA74, 0x1FA74, + 0x1FA75, 0x1FA77, + 0x1FA78, 0x1FA7A, + 0x1FA7B, 0x1FA7C, + 0x1FA7D, 0x1FA7F, + 0x1FA80, 0x1FA82, + 0x1FA83, 0x1FA86, + 0x1FA87, 0x1FA88, + 0x1FA89, 0x1FA8F, + 0x1FA90, 0x1FA95, + 0x1FA96, 0x1FAA8, + 0x1FAA9, 0x1FAAC, + 0x1FAAD, 0x1FAAF, + 0x1FAB0, 0x1FAB6, + 0x1FAB7, 0x1FABA, + 0x1FABB, 0x1FABD, + 0x1FABE, 0x1FABE, + 0x1FABF, 0x1FABF, + 0x1FAC0, 0x1FAC2, + 0x1FAC3, 0x1FAC5, + 0x1FAC6, 0x1FACD, + 0x1FACE, 0x1FACF, + 0x1FAD0, 0x1FAD6, + 0x1FAD7, 0x1FAD9, + 0x1FADA, 0x1FADB, + 0x1FADC, 0x1FADF, + 0x1FAE0, 0x1FAE7, + 0x1FAE8, 0x1FAE8, + 0x1FAE9, 0x1FAEF, + 0x1FAF0, 0x1FAF6, + 0x1FAF7, 0x1FAF8, + 0x1FAF9, 0x1FAFF, + 0x1FC00, 0x1FFFD, +}; + +static const int32_t ucg_grapheme_extend_ranges[] = { + 0x0300, 0x036F, + 0x0483, 0x0487, + 0x0488, 0x0489, + 0x0591, 0x05BD, + 0x05BF, 0x05BF, + 0x05C1, 0x05C2, + 0x05C4, 0x05C5, + 0x05C7, 0x05C7, + 0x0610, 0x061A, + 0x064B, 0x065F, + 0x0670, 0x0670, + 0x06D6, 0x06DC, + 0x06DF, 0x06E4, + 0x06E7, 0x06E8, + 0x06EA, 0x06ED, + 0x0711, 0x0711, + 0x0730, 0x074A, + 0x07A6, 0x07B0, + 0x07EB, 0x07F3, + 0x07FD, 0x07FD, + 0x0816, 0x0819, + 0x081B, 0x0823, + 0x0825, 0x0827, + 0x0829, 0x082D, + 0x0859, 0x085B, + 0x0898, 0x089F, + 0x08CA, 0x08E1, + 0x08E3, 0x0902, + 0x093A, 0x093A, + 0x093C, 0x093C, + 0x0941, 0x0948, + 0x094D, 0x094D, + 0x0951, 0x0957, + 0x0962, 0x0963, + 0x0981, 0x0981, + 0x09BC, 0x09BC, + 0x09BE, 0x09BE, + 0x09C1, 0x09C4, + 0x09CD, 0x09CD, + 0x09D7, 0x09D7, + 0x09E2, 0x09E3, + 0x09FE, 0x09FE, + 0x0A01, 0x0A02, + 0x0A3C, 0x0A3C, + 0x0A41, 0x0A42, + 0x0A47, 0x0A48, + 0x0A4B, 0x0A4D, + 0x0A51, 0x0A51, + 0x0A70, 0x0A71, + 0x0A75, 0x0A75, + 0x0A81, 0x0A82, + 0x0ABC, 0x0ABC, + 0x0AC1, 0x0AC5, + 0x0AC7, 0x0AC8, + 0x0ACD, 0x0ACD, + 0x0AE2, 0x0AE3, + 0x0AFA, 0x0AFF, + 0x0B01, 0x0B01, + 0x0B3C, 0x0B3C, + 0x0B3E, 0x0B3E, + 0x0B3F, 0x0B3F, + 0x0B41, 0x0B44, + 0x0B4D, 0x0B4D, + 0x0B55, 0x0B56, + 0x0B57, 0x0B57, + 0x0B62, 0x0B63, + 0x0B82, 0x0B82, + 0x0BBE, 0x0BBE, + 0x0BC0, 0x0BC0, + 0x0BCD, 0x0BCD, + 0x0BD7, 0x0BD7, + 0x0C00, 0x0C00, + 0x0C04, 0x0C04, + 0x0C3C, 0x0C3C, + 0x0C3E, 0x0C40, + 0x0C46, 0x0C48, + 0x0C4A, 0x0C4D, + 0x0C55, 0x0C56, + 0x0C62, 0x0C63, + 0x0C81, 0x0C81, + 0x0CBC, 0x0CBC, + 0x0CBF, 0x0CBF, + 0x0CC2, 0x0CC2, + 0x0CC6, 0x0CC6, + 0x0CCC, 0x0CCD, + 0x0CD5, 0x0CD6, + 0x0CE2, 0x0CE3, + 0x0D00, 0x0D01, + 0x0D3B, 0x0D3C, + 0x0D3E, 0x0D3E, + 0x0D41, 0x0D44, + 0x0D4D, 0x0D4D, + 0x0D57, 0x0D57, + 0x0D62, 0x0D63, + 0x0D81, 0x0D81, + 0x0DCA, 0x0DCA, + 0x0DCF, 0x0DCF, + 0x0DD2, 0x0DD4, + 0x0DD6, 0x0DD6, + 0x0DDF, 0x0DDF, + 0x0E31, 0x0E31, + 0x0E34, 0x0E3A, + 0x0E47, 0x0E4E, + 0x0EB1, 0x0EB1, + 0x0EB4, 0x0EBC, + 0x0EC8, 0x0ECE, + 0x0F18, 0x0F19, + 0x0F35, 0x0F35, + 0x0F37, 0x0F37, + 0x0F39, 0x0F39, + 0x0F71, 0x0F7E, + 0x0F80, 0x0F84, + 0x0F86, 0x0F87, + 0x0F8D, 0x0F97, + 0x0F99, 0x0FBC, + 0x0FC6, 0x0FC6, + 0x102D, 0x1030, + 0x1032, 0x1037, + 0x1039, 0x103A, + 0x103D, 0x103E, + 0x1058, 0x1059, + 0x105E, 0x1060, + 0x1071, 0x1074, + 0x1082, 0x1082, + 0x1085, 0x1086, + 0x108D, 0x108D, + 0x109D, 0x109D, + 0x135D, 0x135F, + 0x1712, 0x1714, + 0x1732, 0x1733, + 0x1752, 0x1753, + 0x1772, 0x1773, + 0x17B4, 0x17B5, + 0x17B7, 0x17BD, + 0x17C6, 0x17C6, + 0x17C9, 0x17D3, + 0x17DD, 0x17DD, + 0x180B, 0x180D, + 0x180F, 0x180F, + 0x1885, 0x1886, + 0x18A9, 0x18A9, + 0x1920, 0x1922, + 0x1927, 0x1928, + 0x1932, 0x1932, + 0x1939, 0x193B, + 0x1A17, 0x1A18, + 0x1A1B, 0x1A1B, + 0x1A56, 0x1A56, + 0x1A58, 0x1A5E, + 0x1A60, 0x1A60, + 0x1A62, 0x1A62, + 0x1A65, 0x1A6C, + 0x1A73, 0x1A7C, + 0x1A7F, 0x1A7F, + 0x1AB0, 0x1ABD, + 0x1ABE, 0x1ABE, + 0x1ABF, 0x1ACE, + 0x1B00, 0x1B03, + 0x1B34, 0x1B34, + 0x1B35, 0x1B35, + 0x1B36, 0x1B3A, + 0x1B3C, 0x1B3C, + 0x1B42, 0x1B42, + 0x1B6B, 0x1B73, + 0x1B80, 0x1B81, + 0x1BA2, 0x1BA5, + 0x1BA8, 0x1BA9, + 0x1BAB, 0x1BAD, + 0x1BE6, 0x1BE6, + 0x1BE8, 0x1BE9, + 0x1BED, 0x1BED, + 0x1BEF, 0x1BF1, + 0x1C2C, 0x1C33, + 0x1C36, 0x1C37, + 0x1CD0, 0x1CD2, + 0x1CD4, 0x1CE0, + 0x1CE2, 0x1CE8, + 0x1CED, 0x1CED, + 0x1CF4, 0x1CF4, + 0x1CF8, 0x1CF9, + 0x1DC0, 0x1DFF, + 0x200C, 0x200C, + 0x20D0, 0x20DC, + 0x20DD, 0x20E0, + 0x20E1, 0x20E1, + 0x20E2, 0x20E4, + 0x20E5, 0x20F0, + 0x2CEF, 0x2CF1, + 0x2D7F, 0x2D7F, + 0x2DE0, 0x2DFF, + 0x302A, 0x302D, + 0x302E, 0x302F, + 0x3099, 0x309A, + 0xA66F, 0xA66F, + 0xA670, 0xA672, + 0xA674, 0xA67D, + 0xA69E, 0xA69F, + 0xA6F0, 0xA6F1, + 0xA802, 0xA802, + 0xA806, 0xA806, + 0xA80B, 0xA80B, + 0xA825, 0xA826, + 0xA82C, 0xA82C, + 0xA8C4, 0xA8C5, + 0xA8E0, 0xA8F1, + 0xA8FF, 0xA8FF, + 0xA926, 0xA92D, + 0xA947, 0xA951, + 0xA980, 0xA982, + 0xA9B3, 0xA9B3, + 0xA9B6, 0xA9B9, + 0xA9BC, 0xA9BD, + 0xA9E5, 0xA9E5, + 0xAA29, 0xAA2E, + 0xAA31, 0xAA32, + 0xAA35, 0xAA36, + 0xAA43, 0xAA43, + 0xAA4C, 0xAA4C, + 0xAA7C, 0xAA7C, + 0xAAB0, 0xAAB0, + 0xAAB2, 0xAAB4, + 0xAAB7, 0xAAB8, + 0xAABE, 0xAABF, + 0xAAC1, 0xAAC1, + 0xAAEC, 0xAAED, + 0xAAF6, 0xAAF6, + 0xABE5, 0xABE5, + 0xABE8, 0xABE8, + 0xABED, 0xABED, + 0xFB1E, 0xFB1E, + 0xFE00, 0xFE0F, + 0xFE20, 0xFE2F, + 0xFF9E, 0xFF9F, + 0x101FD, 0x101FD, + 0x102E0, 0x102E0, + 0x10376, 0x1037A, + 0x10A01, 0x10A03, + 0x10A05, 0x10A06, + 0x10A0C, 0x10A0F, + 0x10A38, 0x10A3A, + 0x10A3F, 0x10A3F, + 0x10AE5, 0x10AE6, + 0x10D24, 0x10D27, + 0x10EAB, 0x10EAC, + 0x10EFD, 0x10EFF, + 0x10F46, 0x10F50, + 0x10F82, 0x10F85, + 0x11001, 0x11001, + 0x11038, 0x11046, + 0x11070, 0x11070, + 0x11073, 0x11074, + 0x1107F, 0x11081, + 0x110B3, 0x110B6, + 0x110B9, 0x110BA, + 0x110C2, 0x110C2, + 0x11100, 0x11102, + 0x11127, 0x1112B, + 0x1112D, 0x11134, + 0x11173, 0x11173, + 0x11180, 0x11181, + 0x111B6, 0x111BE, + 0x111C9, 0x111CC, + 0x111CF, 0x111CF, + 0x1122F, 0x11231, + 0x11234, 0x11234, + 0x11236, 0x11237, + 0x1123E, 0x1123E, + 0x11241, 0x11241, + 0x112DF, 0x112DF, + 0x112E3, 0x112EA, + 0x11300, 0x11301, + 0x1133B, 0x1133C, + 0x1133E, 0x1133E, + 0x11340, 0x11340, + 0x11357, 0x11357, + 0x11366, 0x1136C, + 0x11370, 0x11374, + 0x11438, 0x1143F, + 0x11442, 0x11444, + 0x11446, 0x11446, + 0x1145E, 0x1145E, + 0x114B0, 0x114B0, + 0x114B3, 0x114B8, + 0x114BA, 0x114BA, + 0x114BD, 0x114BD, + 0x114BF, 0x114C0, + 0x114C2, 0x114C3, + 0x115AF, 0x115AF, + 0x115B2, 0x115B5, + 0x115BC, 0x115BD, + 0x115BF, 0x115C0, + 0x115DC, 0x115DD, + 0x11633, 0x1163A, + 0x1163D, 0x1163D, + 0x1163F, 0x11640, + 0x116AB, 0x116AB, + 0x116AD, 0x116AD, + 0x116B0, 0x116B5, + 0x116B7, 0x116B7, + 0x1171D, 0x1171F, + 0x11722, 0x11725, + 0x11727, 0x1172B, + 0x1182F, 0x11837, + 0x11839, 0x1183A, + 0x11930, 0x11930, + 0x1193B, 0x1193C, + 0x1193E, 0x1193E, + 0x11943, 0x11943, + 0x119D4, 0x119D7, + 0x119DA, 0x119DB, + 0x119E0, 0x119E0, + 0x11A01, 0x11A0A, + 0x11A33, 0x11A38, + 0x11A3B, 0x11A3E, + 0x11A47, 0x11A47, + 0x11A51, 0x11A56, + 0x11A59, 0x11A5B, + 0x11A8A, 0x11A96, + 0x11A98, 0x11A99, + 0x11C30, 0x11C36, + 0x11C38, 0x11C3D, + 0x11C3F, 0x11C3F, + 0x11C92, 0x11CA7, + 0x11CAA, 0x11CB0, + 0x11CB2, 0x11CB3, + 0x11CB5, 0x11CB6, + 0x11D31, 0x11D36, + 0x11D3A, 0x11D3A, + 0x11D3C, 0x11D3D, + 0x11D3F, 0x11D45, + 0x11D47, 0x11D47, + 0x11D90, 0x11D91, + 0x11D95, 0x11D95, + 0x11D97, 0x11D97, + 0x11EF3, 0x11EF4, + 0x11F00, 0x11F01, + 0x11F36, 0x11F3A, + 0x11F40, 0x11F40, + 0x11F42, 0x11F42, + 0x13440, 0x13440, + 0x13447, 0x13455, + 0x16AF0, 0x16AF4, + 0x16B30, 0x16B36, + 0x16F4F, 0x16F4F, + 0x16F8F, 0x16F92, + 0x16FE4, 0x16FE4, + 0x1BC9D, 0x1BC9E, + 0x1CF00, 0x1CF2D, + 0x1CF30, 0x1CF46, + 0x1D165, 0x1D165, + 0x1D167, 0x1D169, + 0x1D16E, 0x1D172, + 0x1D17B, 0x1D182, + 0x1D185, 0x1D18B, + 0x1D1AA, 0x1D1AD, + 0x1D242, 0x1D244, + 0x1DA00, 0x1DA36, + 0x1DA3B, 0x1DA6C, + 0x1DA75, 0x1DA75, + 0x1DA84, 0x1DA84, + 0x1DA9B, 0x1DA9F, + 0x1DAA1, 0x1DAAF, + 0x1E000, 0x1E006, + 0x1E008, 0x1E018, + 0x1E01B, 0x1E021, + 0x1E023, 0x1E024, + 0x1E026, 0x1E02A, + 0x1E08F, 0x1E08F, + 0x1E130, 0x1E136, + 0x1E2AE, 0x1E2AE, + 0x1E2EC, 0x1E2EF, + 0x1E4EC, 0x1E4EF, + 0x1E8D0, 0x1E8D6, + 0x1E944, 0x1E94A, + 0xE0020, 0xE007F, + 0xE0100, 0xE01EF, +}; + +static const int32_t ucg_hangul_syllable_lv_singlets[] = { + 0xAC00, + 0xAC1C, + 0xAC38, + 0xAC54, + 0xAC70, + 0xAC8C, + 0xACA8, + 0xACC4, + 0xACE0, + 0xACFC, + 0xAD18, + 0xAD34, + 0xAD50, + 0xAD6C, + 0xAD88, + 0xADA4, + 0xADC0, + 0xADDC, + 0xADF8, + 0xAE14, + 0xAE30, + 0xAE4C, + 0xAE68, + 0xAE84, + 0xAEA0, + 0xAEBC, + 0xAED8, + 0xAEF4, + 0xAF10, + 0xAF2C, + 0xAF48, + 0xAF64, + 0xAF80, + 0xAF9C, + 0xAFB8, + 0xAFD4, + 0xAFF0, + 0xB00C, + 0xB028, + 0xB044, + 0xB060, + 0xB07C, + 0xB098, + 0xB0B4, + 0xB0D0, + 0xB0EC, + 0xB108, + 0xB124, + 0xB140, + 0xB15C, + 0xB178, + 0xB194, + 0xB1B0, + 0xB1CC, + 0xB1E8, + 0xB204, + 0xB220, + 0xB23C, + 0xB258, + 0xB274, + 0xB290, + 0xB2AC, + 0xB2C8, + 0xB2E4, + 0xB300, + 0xB31C, + 0xB338, + 0xB354, + 0xB370, + 0xB38C, + 0xB3A8, + 0xB3C4, + 0xB3E0, + 0xB3FC, + 0xB418, + 0xB434, + 0xB450, + 0xB46C, + 0xB488, + 0xB4A4, + 0xB4C0, + 0xB4DC, + 0xB4F8, + 0xB514, + 0xB530, + 0xB54C, + 0xB568, + 0xB584, + 0xB5A0, + 0xB5BC, + 0xB5D8, + 0xB5F4, + 0xB610, + 0xB62C, + 0xB648, + 0xB664, + 0xB680, + 0xB69C, + 0xB6B8, + 0xB6D4, + 0xB6F0, + 0xB70C, + 0xB728, + 0xB744, + 0xB760, + 0xB77C, + 0xB798, + 0xB7B4, + 0xB7D0, + 0xB7EC, + 0xB808, + 0xB824, + 0xB840, + 0xB85C, + 0xB878, + 0xB894, + 0xB8B0, + 0xB8CC, + 0xB8E8, + 0xB904, + 0xB920, + 0xB93C, + 0xB958, + 0xB974, + 0xB990, + 0xB9AC, + 0xB9C8, + 0xB9E4, + 0xBA00, + 0xBA1C, + 0xBA38, + 0xBA54, + 0xBA70, + 0xBA8C, + 0xBAA8, + 0xBAC4, + 0xBAE0, + 0xBAFC, + 0xBB18, + 0xBB34, + 0xBB50, + 0xBB6C, + 0xBB88, + 0xBBA4, + 0xBBC0, + 0xBBDC, + 0xBBF8, + 0xBC14, + 0xBC30, + 0xBC4C, + 0xBC68, + 0xBC84, + 0xBCA0, + 0xBCBC, + 0xBCD8, + 0xBCF4, + 0xBD10, + 0xBD2C, + 0xBD48, + 0xBD64, + 0xBD80, + 0xBD9C, + 0xBDB8, + 0xBDD4, + 0xBDF0, + 0xBE0C, + 0xBE28, + 0xBE44, + 0xBE60, + 0xBE7C, + 0xBE98, + 0xBEB4, + 0xBED0, + 0xBEEC, + 0xBF08, + 0xBF24, + 0xBF40, + 0xBF5C, + 0xBF78, + 0xBF94, + 0xBFB0, + 0xBFCC, + 0xBFE8, + 0xC004, + 0xC020, + 0xC03C, + 0xC058, + 0xC074, + 0xC090, + 0xC0AC, + 0xC0C8, + 0xC0E4, + 0xC100, + 0xC11C, + 0xC138, + 0xC154, + 0xC170, + 0xC18C, + 0xC1A8, + 0xC1C4, + 0xC1E0, + 0xC1FC, + 0xC218, + 0xC234, + 0xC250, + 0xC26C, + 0xC288, + 0xC2A4, + 0xC2C0, + 0xC2DC, + 0xC2F8, + 0xC314, + 0xC330, + 0xC34C, + 0xC368, + 0xC384, + 0xC3A0, + 0xC3BC, + 0xC3D8, + 0xC3F4, + 0xC410, + 0xC42C, + 0xC448, + 0xC464, + 0xC480, + 0xC49C, + 0xC4B8, + 0xC4D4, + 0xC4F0, + 0xC50C, + 0xC528, + 0xC544, + 0xC560, + 0xC57C, + 0xC598, + 0xC5B4, + 0xC5D0, + 0xC5EC, + 0xC608, + 0xC624, + 0xC640, + 0xC65C, + 0xC678, + 0xC694, + 0xC6B0, + 0xC6CC, + 0xC6E8, + 0xC704, + 0xC720, + 0xC73C, + 0xC758, + 0xC774, + 0xC790, + 0xC7AC, + 0xC7C8, + 0xC7E4, + 0xC800, + 0xC81C, + 0xC838, + 0xC854, + 0xC870, + 0xC88C, + 0xC8A8, + 0xC8C4, + 0xC8E0, + 0xC8FC, + 0xC918, + 0xC934, + 0xC950, + 0xC96C, + 0xC988, + 0xC9A4, + 0xC9C0, + 0xC9DC, + 0xC9F8, + 0xCA14, + 0xCA30, + 0xCA4C, + 0xCA68, + 0xCA84, + 0xCAA0, + 0xCABC, + 0xCAD8, + 0xCAF4, + 0xCB10, + 0xCB2C, + 0xCB48, + 0xCB64, + 0xCB80, + 0xCB9C, + 0xCBB8, + 0xCBD4, + 0xCBF0, + 0xCC0C, + 0xCC28, + 0xCC44, + 0xCC60, + 0xCC7C, + 0xCC98, + 0xCCB4, + 0xCCD0, + 0xCCEC, + 0xCD08, + 0xCD24, + 0xCD40, + 0xCD5C, + 0xCD78, + 0xCD94, + 0xCDB0, + 0xCDCC, + 0xCDE8, + 0xCE04, + 0xCE20, + 0xCE3C, + 0xCE58, + 0xCE74, + 0xCE90, + 0xCEAC, + 0xCEC8, + 0xCEE4, + 0xCF00, + 0xCF1C, + 0xCF38, + 0xCF54, + 0xCF70, + 0xCF8C, + 0xCFA8, + 0xCFC4, + 0xCFE0, + 0xCFFC, + 0xD018, + 0xD034, + 0xD050, + 0xD06C, + 0xD088, + 0xD0A4, + 0xD0C0, + 0xD0DC, + 0xD0F8, + 0xD114, + 0xD130, + 0xD14C, + 0xD168, + 0xD184, + 0xD1A0, + 0xD1BC, + 0xD1D8, + 0xD1F4, + 0xD210, + 0xD22C, + 0xD248, + 0xD264, + 0xD280, + 0xD29C, + 0xD2B8, + 0xD2D4, + 0xD2F0, + 0xD30C, + 0xD328, + 0xD344, + 0xD360, + 0xD37C, + 0xD398, + 0xD3B4, + 0xD3D0, + 0xD3EC, + 0xD408, + 0xD424, + 0xD440, + 0xD45C, + 0xD478, + 0xD494, + 0xD4B0, + 0xD4CC, + 0xD4E8, + 0xD504, + 0xD520, + 0xD53C, + 0xD558, + 0xD574, + 0xD590, + 0xD5AC, + 0xD5C8, + 0xD5E4, + 0xD600, + 0xD61C, + 0xD638, + 0xD654, + 0xD670, + 0xD68C, + 0xD6A8, + 0xD6C4, + 0xD6E0, + 0xD6FC, + 0xD718, + 0xD734, + 0xD750, + 0xD76C, + 0xD788, +}; + +static const int32_t ucg_hangul_syllable_lvt_ranges[] = { + 0xAC01, 0xAC1B, + 0xAC1D, 0xAC37, + 0xAC39, 0xAC53, + 0xAC55, 0xAC6F, + 0xAC71, 0xAC8B, + 0xAC8D, 0xACA7, + 0xACA9, 0xACC3, + 0xACC5, 0xACDF, + 0xACE1, 0xACFB, + 0xACFD, 0xAD17, + 0xAD19, 0xAD33, + 0xAD35, 0xAD4F, + 0xAD51, 0xAD6B, + 0xAD6D, 0xAD87, + 0xAD89, 0xADA3, + 0xADA5, 0xADBF, + 0xADC1, 0xADDB, + 0xADDD, 0xADF7, + 0xADF9, 0xAE13, + 0xAE15, 0xAE2F, + 0xAE31, 0xAE4B, + 0xAE4D, 0xAE67, + 0xAE69, 0xAE83, + 0xAE85, 0xAE9F, + 0xAEA1, 0xAEBB, + 0xAEBD, 0xAED7, + 0xAED9, 0xAEF3, + 0xAEF5, 0xAF0F, + 0xAF11, 0xAF2B, + 0xAF2D, 0xAF47, + 0xAF49, 0xAF63, + 0xAF65, 0xAF7F, + 0xAF81, 0xAF9B, + 0xAF9D, 0xAFB7, + 0xAFB9, 0xAFD3, + 0xAFD5, 0xAFEF, + 0xAFF1, 0xB00B, + 0xB00D, 0xB027, + 0xB029, 0xB043, + 0xB045, 0xB05F, + 0xB061, 0xB07B, + 0xB07D, 0xB097, + 0xB099, 0xB0B3, + 0xB0B5, 0xB0CF, + 0xB0D1, 0xB0EB, + 0xB0ED, 0xB107, + 0xB109, 0xB123, + 0xB125, 0xB13F, + 0xB141, 0xB15B, + 0xB15D, 0xB177, + 0xB179, 0xB193, + 0xB195, 0xB1AF, + 0xB1B1, 0xB1CB, + 0xB1CD, 0xB1E7, + 0xB1E9, 0xB203, + 0xB205, 0xB21F, + 0xB221, 0xB23B, + 0xB23D, 0xB257, + 0xB259, 0xB273, + 0xB275, 0xB28F, + 0xB291, 0xB2AB, + 0xB2AD, 0xB2C7, + 0xB2C9, 0xB2E3, + 0xB2E5, 0xB2FF, + 0xB301, 0xB31B, + 0xB31D, 0xB337, + 0xB339, 0xB353, + 0xB355, 0xB36F, + 0xB371, 0xB38B, + 0xB38D, 0xB3A7, + 0xB3A9, 0xB3C3, + 0xB3C5, 0xB3DF, + 0xB3E1, 0xB3FB, + 0xB3FD, 0xB417, + 0xB419, 0xB433, + 0xB435, 0xB44F, + 0xB451, 0xB46B, + 0xB46D, 0xB487, + 0xB489, 0xB4A3, + 0xB4A5, 0xB4BF, + 0xB4C1, 0xB4DB, + 0xB4DD, 0xB4F7, + 0xB4F9, 0xB513, + 0xB515, 0xB52F, + 0xB531, 0xB54B, + 0xB54D, 0xB567, + 0xB569, 0xB583, + 0xB585, 0xB59F, + 0xB5A1, 0xB5BB, + 0xB5BD, 0xB5D7, + 0xB5D9, 0xB5F3, + 0xB5F5, 0xB60F, + 0xB611, 0xB62B, + 0xB62D, 0xB647, + 0xB649, 0xB663, + 0xB665, 0xB67F, + 0xB681, 0xB69B, + 0xB69D, 0xB6B7, + 0xB6B9, 0xB6D3, + 0xB6D5, 0xB6EF, + 0xB6F1, 0xB70B, + 0xB70D, 0xB727, + 0xB729, 0xB743, + 0xB745, 0xB75F, + 0xB761, 0xB77B, + 0xB77D, 0xB797, + 0xB799, 0xB7B3, + 0xB7B5, 0xB7CF, + 0xB7D1, 0xB7EB, + 0xB7ED, 0xB807, + 0xB809, 0xB823, + 0xB825, 0xB83F, + 0xB841, 0xB85B, + 0xB85D, 0xB877, + 0xB879, 0xB893, + 0xB895, 0xB8AF, + 0xB8B1, 0xB8CB, + 0xB8CD, 0xB8E7, + 0xB8E9, 0xB903, + 0xB905, 0xB91F, + 0xB921, 0xB93B, + 0xB93D, 0xB957, + 0xB959, 0xB973, + 0xB975, 0xB98F, + 0xB991, 0xB9AB, + 0xB9AD, 0xB9C7, + 0xB9C9, 0xB9E3, + 0xB9E5, 0xB9FF, + 0xBA01, 0xBA1B, + 0xBA1D, 0xBA37, + 0xBA39, 0xBA53, + 0xBA55, 0xBA6F, + 0xBA71, 0xBA8B, + 0xBA8D, 0xBAA7, + 0xBAA9, 0xBAC3, + 0xBAC5, 0xBADF, + 0xBAE1, 0xBAFB, + 0xBAFD, 0xBB17, + 0xBB19, 0xBB33, + 0xBB35, 0xBB4F, + 0xBB51, 0xBB6B, + 0xBB6D, 0xBB87, + 0xBB89, 0xBBA3, + 0xBBA5, 0xBBBF, + 0xBBC1, 0xBBDB, + 0xBBDD, 0xBBF7, + 0xBBF9, 0xBC13, + 0xBC15, 0xBC2F, + 0xBC31, 0xBC4B, + 0xBC4D, 0xBC67, + 0xBC69, 0xBC83, + 0xBC85, 0xBC9F, + 0xBCA1, 0xBCBB, + 0xBCBD, 0xBCD7, + 0xBCD9, 0xBCF3, + 0xBCF5, 0xBD0F, + 0xBD11, 0xBD2B, + 0xBD2D, 0xBD47, + 0xBD49, 0xBD63, + 0xBD65, 0xBD7F, + 0xBD81, 0xBD9B, + 0xBD9D, 0xBDB7, + 0xBDB9, 0xBDD3, + 0xBDD5, 0xBDEF, + 0xBDF1, 0xBE0B, + 0xBE0D, 0xBE27, + 0xBE29, 0xBE43, + 0xBE45, 0xBE5F, + 0xBE61, 0xBE7B, + 0xBE7D, 0xBE97, + 0xBE99, 0xBEB3, + 0xBEB5, 0xBECF, + 0xBED1, 0xBEEB, + 0xBEED, 0xBF07, + 0xBF09, 0xBF23, + 0xBF25, 0xBF3F, + 0xBF41, 0xBF5B, + 0xBF5D, 0xBF77, + 0xBF79, 0xBF93, + 0xBF95, 0xBFAF, + 0xBFB1, 0xBFCB, + 0xBFCD, 0xBFE7, + 0xBFE9, 0xC003, + 0xC005, 0xC01F, + 0xC021, 0xC03B, + 0xC03D, 0xC057, + 0xC059, 0xC073, + 0xC075, 0xC08F, + 0xC091, 0xC0AB, + 0xC0AD, 0xC0C7, + 0xC0C9, 0xC0E3, + 0xC0E5, 0xC0FF, + 0xC101, 0xC11B, + 0xC11D, 0xC137, + 0xC139, 0xC153, + 0xC155, 0xC16F, + 0xC171, 0xC18B, + 0xC18D, 0xC1A7, + 0xC1A9, 0xC1C3, + 0xC1C5, 0xC1DF, + 0xC1E1, 0xC1FB, + 0xC1FD, 0xC217, + 0xC219, 0xC233, + 0xC235, 0xC24F, + 0xC251, 0xC26B, + 0xC26D, 0xC287, + 0xC289, 0xC2A3, + 0xC2A5, 0xC2BF, + 0xC2C1, 0xC2DB, + 0xC2DD, 0xC2F7, + 0xC2F9, 0xC313, + 0xC315, 0xC32F, + 0xC331, 0xC34B, + 0xC34D, 0xC367, + 0xC369, 0xC383, + 0xC385, 0xC39F, + 0xC3A1, 0xC3BB, + 0xC3BD, 0xC3D7, + 0xC3D9, 0xC3F3, + 0xC3F5, 0xC40F, + 0xC411, 0xC42B, + 0xC42D, 0xC447, + 0xC449, 0xC463, + 0xC465, 0xC47F, + 0xC481, 0xC49B, + 0xC49D, 0xC4B7, + 0xC4B9, 0xC4D3, + 0xC4D5, 0xC4EF, + 0xC4F1, 0xC50B, + 0xC50D, 0xC527, + 0xC529, 0xC543, + 0xC545, 0xC55F, + 0xC561, 0xC57B, + 0xC57D, 0xC597, + 0xC599, 0xC5B3, + 0xC5B5, 0xC5CF, + 0xC5D1, 0xC5EB, + 0xC5ED, 0xC607, + 0xC609, 0xC623, + 0xC625, 0xC63F, + 0xC641, 0xC65B, + 0xC65D, 0xC677, + 0xC679, 0xC693, + 0xC695, 0xC6AF, + 0xC6B1, 0xC6CB, + 0xC6CD, 0xC6E7, + 0xC6E9, 0xC703, + 0xC705, 0xC71F, + 0xC721, 0xC73B, + 0xC73D, 0xC757, + 0xC759, 0xC773, + 0xC775, 0xC78F, + 0xC791, 0xC7AB, + 0xC7AD, 0xC7C7, + 0xC7C9, 0xC7E3, + 0xC7E5, 0xC7FF, + 0xC801, 0xC81B, + 0xC81D, 0xC837, + 0xC839, 0xC853, + 0xC855, 0xC86F, + 0xC871, 0xC88B, + 0xC88D, 0xC8A7, + 0xC8A9, 0xC8C3, + 0xC8C5, 0xC8DF, + 0xC8E1, 0xC8FB, + 0xC8FD, 0xC917, + 0xC919, 0xC933, + 0xC935, 0xC94F, + 0xC951, 0xC96B, + 0xC96D, 0xC987, + 0xC989, 0xC9A3, + 0xC9A5, 0xC9BF, + 0xC9C1, 0xC9DB, + 0xC9DD, 0xC9F7, + 0xC9F9, 0xCA13, + 0xCA15, 0xCA2F, + 0xCA31, 0xCA4B, + 0xCA4D, 0xCA67, + 0xCA69, 0xCA83, + 0xCA85, 0xCA9F, + 0xCAA1, 0xCABB, + 0xCABD, 0xCAD7, + 0xCAD9, 0xCAF3, + 0xCAF5, 0xCB0F, + 0xCB11, 0xCB2B, + 0xCB2D, 0xCB47, + 0xCB49, 0xCB63, + 0xCB65, 0xCB7F, + 0xCB81, 0xCB9B, + 0xCB9D, 0xCBB7, + 0xCBB9, 0xCBD3, + 0xCBD5, 0xCBEF, + 0xCBF1, 0xCC0B, + 0xCC0D, 0xCC27, + 0xCC29, 0xCC43, + 0xCC45, 0xCC5F, + 0xCC61, 0xCC7B, + 0xCC7D, 0xCC97, + 0xCC99, 0xCCB3, + 0xCCB5, 0xCCCF, + 0xCCD1, 0xCCEB, + 0xCCED, 0xCD07, + 0xCD09, 0xCD23, + 0xCD25, 0xCD3F, + 0xCD41, 0xCD5B, + 0xCD5D, 0xCD77, + 0xCD79, 0xCD93, + 0xCD95, 0xCDAF, + 0xCDB1, 0xCDCB, + 0xCDCD, 0xCDE7, + 0xCDE9, 0xCE03, + 0xCE05, 0xCE1F, + 0xCE21, 0xCE3B, + 0xCE3D, 0xCE57, + 0xCE59, 0xCE73, + 0xCE75, 0xCE8F, + 0xCE91, 0xCEAB, + 0xCEAD, 0xCEC7, + 0xCEC9, 0xCEE3, + 0xCEE5, 0xCEFF, + 0xCF01, 0xCF1B, + 0xCF1D, 0xCF37, + 0xCF39, 0xCF53, + 0xCF55, 0xCF6F, + 0xCF71, 0xCF8B, + 0xCF8D, 0xCFA7, + 0xCFA9, 0xCFC3, + 0xCFC5, 0xCFDF, + 0xCFE1, 0xCFFB, + 0xCFFD, 0xD017, + 0xD019, 0xD033, + 0xD035, 0xD04F, + 0xD051, 0xD06B, + 0xD06D, 0xD087, + 0xD089, 0xD0A3, + 0xD0A5, 0xD0BF, + 0xD0C1, 0xD0DB, + 0xD0DD, 0xD0F7, + 0xD0F9, 0xD113, + 0xD115, 0xD12F, + 0xD131, 0xD14B, + 0xD14D, 0xD167, + 0xD169, 0xD183, + 0xD185, 0xD19F, + 0xD1A1, 0xD1BB, + 0xD1BD, 0xD1D7, + 0xD1D9, 0xD1F3, + 0xD1F5, 0xD20F, + 0xD211, 0xD22B, + 0xD22D, 0xD247, + 0xD249, 0xD263, + 0xD265, 0xD27F, + 0xD281, 0xD29B, + 0xD29D, 0xD2B7, + 0xD2B9, 0xD2D3, + 0xD2D5, 0xD2EF, + 0xD2F1, 0xD30B, + 0xD30D, 0xD327, + 0xD329, 0xD343, + 0xD345, 0xD35F, + 0xD361, 0xD37B, + 0xD37D, 0xD397, + 0xD399, 0xD3B3, + 0xD3B5, 0xD3CF, + 0xD3D1, 0xD3EB, + 0xD3ED, 0xD407, + 0xD409, 0xD423, + 0xD425, 0xD43F, + 0xD441, 0xD45B, + 0xD45D, 0xD477, + 0xD479, 0xD493, + 0xD495, 0xD4AF, + 0xD4B1, 0xD4CB, + 0xD4CD, 0xD4E7, + 0xD4E9, 0xD503, + 0xD505, 0xD51F, + 0xD521, 0xD53B, + 0xD53D, 0xD557, + 0xD559, 0xD573, + 0xD575, 0xD58F, + 0xD591, 0xD5AB, + 0xD5AD, 0xD5C7, + 0xD5C9, 0xD5E3, + 0xD5E5, 0xD5FF, + 0xD601, 0xD61B, + 0xD61D, 0xD637, + 0xD639, 0xD653, + 0xD655, 0xD66F, + 0xD671, 0xD68B, + 0xD68D, 0xD6A7, + 0xD6A9, 0xD6C3, + 0xD6C5, 0xD6DF, + 0xD6E1, 0xD6FB, + 0xD6FD, 0xD717, + 0xD719, 0xD733, + 0xD735, 0xD74F, + 0xD751, 0xD76B, + 0xD76D, 0xD787, + 0xD789, 0xD7A3, +}; + +static const int32_t ucg_indic_conjunct_break_consonant_ranges[] = { + 0x0915, 0x0939, + 0x0958, 0x095F, + 0x0978, 0x097F, + 0x0995, 0x09A8, + 0x09AA, 0x09B0, + 0x09B2, 0x09B2, + 0x09B6, 0x09B9, + 0x09DC, 0x09DD, + 0x09DF, 0x09DF, + 0x09F0, 0x09F1, + 0x0A95, 0x0AA8, + 0x0AAA, 0x0AB0, + 0x0AB2, 0x0AB3, + 0x0AB5, 0x0AB9, + 0x0AF9, 0x0AF9, + 0x0B15, 0x0B28, + 0x0B2A, 0x0B30, + 0x0B32, 0x0B33, + 0x0B35, 0x0B39, + 0x0B5C, 0x0B5D, + 0x0B5F, 0x0B5F, + 0x0B71, 0x0B71, + 0x0C15, 0x0C28, + 0x0C2A, 0x0C39, + 0x0C58, 0x0C5A, + 0x0D15, 0x0D3A, +}; + +static const int32_t ucg_indic_conjunct_break_extend_ranges[] = { + 0x0300, 0x034E, + 0x0350, 0x036F, + 0x0483, 0x0487, + 0x0591, 0x05BD, + 0x05BF, 0x05BF, + 0x05C1, 0x05C2, + 0x05C4, 0x05C5, + 0x05C7, 0x05C7, + 0x0610, 0x061A, + 0x064B, 0x065F, + 0x0670, 0x0670, + 0x06D6, 0x06DC, + 0x06DF, 0x06E4, + 0x06E7, 0x06E8, + 0x06EA, 0x06ED, + 0x0711, 0x0711, + 0x0730, 0x074A, + 0x07EB, 0x07F3, + 0x07FD, 0x07FD, + 0x0816, 0x0819, + 0x081B, 0x0823, + 0x0825, 0x0827, + 0x0829, 0x082D, + 0x0859, 0x085B, + 0x0898, 0x089F, + 0x08CA, 0x08E1, + 0x08E3, 0x08FF, + 0x093C, 0x093C, + 0x0951, 0x0954, + 0x09BC, 0x09BC, + 0x09FE, 0x09FE, + 0x0A3C, 0x0A3C, + 0x0ABC, 0x0ABC, + 0x0B3C, 0x0B3C, + 0x0C3C, 0x0C3C, + 0x0C55, 0x0C56, + 0x0CBC, 0x0CBC, + 0x0D3B, 0x0D3C, + 0x0E38, 0x0E3A, + 0x0E48, 0x0E4B, + 0x0EB8, 0x0EBA, + 0x0EC8, 0x0ECB, + 0x0F18, 0x0F19, + 0x0F35, 0x0F35, + 0x0F37, 0x0F37, + 0x0F39, 0x0F39, + 0x0F71, 0x0F72, + 0x0F74, 0x0F74, + 0x0F7A, 0x0F7D, + 0x0F80, 0x0F80, + 0x0F82, 0x0F84, + 0x0F86, 0x0F87, + 0x0FC6, 0x0FC6, + 0x1037, 0x1037, + 0x1039, 0x103A, + 0x108D, 0x108D, + 0x135D, 0x135F, + 0x1714, 0x1714, + 0x17D2, 0x17D2, + 0x17DD, 0x17DD, + 0x18A9, 0x18A9, + 0x1939, 0x193B, + 0x1A17, 0x1A18, + 0x1A60, 0x1A60, + 0x1A75, 0x1A7C, + 0x1A7F, 0x1A7F, + 0x1AB0, 0x1ABD, + 0x1ABF, 0x1ACE, + 0x1B34, 0x1B34, + 0x1B6B, 0x1B73, + 0x1BAB, 0x1BAB, + 0x1BE6, 0x1BE6, + 0x1C37, 0x1C37, + 0x1CD0, 0x1CD2, + 0x1CD4, 0x1CE0, + 0x1CE2, 0x1CE8, + 0x1CED, 0x1CED, + 0x1CF4, 0x1CF4, + 0x1CF8, 0x1CF9, + 0x1DC0, 0x1DFF, + 0x200D, 0x200D, + 0x20D0, 0x20DC, + 0x20E1, 0x20E1, + 0x20E5, 0x20F0, + 0x2CEF, 0x2CF1, + 0x2D7F, 0x2D7F, + 0x2DE0, 0x2DFF, + 0x302A, 0x302D, + 0x302E, 0x302F, + 0x3099, 0x309A, + 0xA66F, 0xA66F, + 0xA674, 0xA67D, + 0xA69E, 0xA69F, + 0xA6F0, 0xA6F1, + 0xA82C, 0xA82C, + 0xA8E0, 0xA8F1, + 0xA92B, 0xA92D, + 0xA9B3, 0xA9B3, + 0xAAB0, 0xAAB0, + 0xAAB2, 0xAAB4, + 0xAAB7, 0xAAB8, + 0xAABE, 0xAABF, + 0xAAC1, 0xAAC1, + 0xAAF6, 0xAAF6, + 0xABED, 0xABED, + 0xFB1E, 0xFB1E, + 0xFE20, 0xFE2F, + 0x101FD, 0x101FD, + 0x102E0, 0x102E0, + 0x10376, 0x1037A, + 0x10A0D, 0x10A0D, + 0x10A0F, 0x10A0F, + 0x10A38, 0x10A3A, + 0x10A3F, 0x10A3F, + 0x10AE5, 0x10AE6, + 0x10D24, 0x10D27, + 0x10EAB, 0x10EAC, + 0x10EFD, 0x10EFF, + 0x10F46, 0x10F50, + 0x10F82, 0x10F85, + 0x11070, 0x11070, + 0x1107F, 0x1107F, + 0x110BA, 0x110BA, + 0x11100, 0x11102, + 0x11133, 0x11134, + 0x11173, 0x11173, + 0x111CA, 0x111CA, + 0x11236, 0x11236, + 0x112E9, 0x112EA, + 0x1133B, 0x1133C, + 0x11366, 0x1136C, + 0x11370, 0x11374, + 0x11446, 0x11446, + 0x1145E, 0x1145E, + 0x114C3, 0x114C3, + 0x115C0, 0x115C0, + 0x116B7, 0x116B7, + 0x1172B, 0x1172B, + 0x1183A, 0x1183A, + 0x1193E, 0x1193E, + 0x11943, 0x11943, + 0x11A34, 0x11A34, + 0x11A47, 0x11A47, + 0x11A99, 0x11A99, + 0x11D42, 0x11D42, + 0x11D44, 0x11D45, + 0x11D97, 0x11D97, + 0x11F42, 0x11F42, + 0x16AF0, 0x16AF4, + 0x16B30, 0x16B36, + 0x1BC9E, 0x1BC9E, + 0x1D165, 0x1D165, + 0x1D167, 0x1D169, + 0x1D16E, 0x1D172, + 0x1D17B, 0x1D182, + 0x1D185, 0x1D18B, + 0x1D1AA, 0x1D1AD, + 0x1D242, 0x1D244, + 0x1E000, 0x1E006, + 0x1E008, 0x1E018, + 0x1E01B, 0x1E021, + 0x1E023, 0x1E024, + 0x1E026, 0x1E02A, + 0x1E08F, 0x1E08F, + 0x1E130, 0x1E136, + 0x1E2AE, 0x1E2AE, + 0x1E2EC, 0x1E2EF, + 0x1E4EC, 0x1E4EF, + 0x1E8D0, 0x1E8D6, + 0x1E944, 0x1E94A, +}; + +// Fullwidth (F) and Wide (W) are counted as 2. +// Everything else is 1. +// +// Derived from: https://unicode.org/Public/15.1.0/ucd/EastAsianWidth.txt +static const int32_t ucg_normalized_east_asian_width_ranges[] = { + 0x0000, 0x10FF, 1, + 0x1100, 0x115F, 2, + 0x1160, 0x2319, 1, + 0x231A, 0x231B, 2, + 0x231C, 0x2328, 1, + 0x2329, 0x232A, 2, + 0x232B, 0x23E8, 1, + 0x23E9, 0x23EC, 2, + 0x23ED, 0x23EF, 1, + 0x23F0, 0x23F0, 2, + 0x23F1, 0x23F2, 1, + 0x23F3, 0x23F3, 2, + 0x23F4, 0x25FC, 1, + 0x25FD, 0x25FE, 2, + 0x25FF, 0x2613, 1, + 0x2614, 0x2615, 2, + 0x2616, 0x2647, 1, + 0x2648, 0x2653, 2, + 0x2654, 0x267E, 1, + 0x267F, 0x267F, 2, + 0x2680, 0x2692, 1, + 0x2693, 0x2693, 2, + 0x2694, 0x26A0, 1, + 0x26A1, 0x26A1, 2, + 0x26A2, 0x26A9, 1, + 0x26AA, 0x26AB, 2, + 0x26AC, 0x26BC, 1, + 0x26BD, 0x26BE, 2, + 0x26BF, 0x26C3, 1, + 0x26C4, 0x26C5, 2, + 0x26C6, 0x26CD, 1, + 0x26CE, 0x26CE, 2, + 0x26CF, 0x26D3, 1, + 0x26D4, 0x26D4, 2, + 0x26D5, 0x26E9, 1, + 0x26EA, 0x26EA, 2, + 0x26EB, 0x26F1, 1, + 0x26F2, 0x26F3, 2, + 0x26F4, 0x26F4, 1, + 0x26F5, 0x26F5, 2, + 0x26F6, 0x26F9, 1, + 0x26FA, 0x26FA, 2, + 0x26FB, 0x26FC, 1, + 0x26FD, 0x26FD, 2, + 0x26FE, 0x2704, 1, + 0x2705, 0x2705, 2, + 0x2706, 0x2709, 1, + 0x270A, 0x270B, 2, + 0x270C, 0x2727, 1, + 0x2728, 0x2728, 2, + 0x2729, 0x274B, 1, + 0x274C, 0x274C, 2, + 0x274D, 0x274D, 1, + 0x274E, 0x274E, 2, + 0x274F, 0x2752, 1, + 0x2753, 0x2755, 2, + 0x2756, 0x2756, 1, + 0x2757, 0x2757, 2, + 0x2758, 0x2794, 1, + 0x2795, 0x2797, 2, + 0x2798, 0x27AF, 1, + 0x27B0, 0x27B0, 2, + 0x27B1, 0x27BE, 1, + 0x27BF, 0x27BF, 2, + 0x27C0, 0x2B1A, 1, + 0x2B1B, 0x2B1C, 2, + 0x2B1D, 0x2B4F, 1, + 0x2B50, 0x2B50, 2, + 0x2B51, 0x2B54, 1, + 0x2B55, 0x2B55, 2, + 0x2B56, 0x2E5D, 1, + 0x2E80, 0x303E, 2, + 0x303F, 0x303F, 1, + 0x3041, 0x3247, 2, + 0x3248, 0x324F, 1, + 0x3250, 0x4DBF, 2, + 0x4DC0, 0x4DFF, 1, + 0x4E00, 0xA4C6, 2, + 0xA4D0, 0xA95F, 1, + 0xA960, 0xA97C, 2, + 0xA980, 0xABF9, 1, + 0xAC00, 0xD7A3, 2, + 0xD7B0, 0xF8FF, 1, + 0xF900, 0xFAFF, 2, + 0xFB00, 0xFE0F, 1, + 0xFE10, 0xFE19, 2, + 0xFE20, 0xFE2F, 1, + 0xFE30, 0xFE6B, 2, + 0xFE70, 0xFEFF, 1, + 0xFF01, 0xFF60, 2, + 0xFF61, 0xFFDC, 1, + 0xFFE0, 0xFFE6, 2, + 0xFFE8, 0x16F9F, 1, + 0x16FE0, 0x1B2FB, 2, + 0x1BC00, 0x1F003, 1, + 0x1F004, 0x1F004, 2, + 0x1F005, 0x1F0CE, 1, + 0x1F0CF, 0x1F0CF, 2, + 0x1F0D1, 0x1F18D, 1, + 0x1F18E, 0x1F18E, 2, + 0x1F18F, 0x1F190, 1, + 0x1F191, 0x1F19A, 2, + 0x1F19B, 0x1F1FF, 1, + 0x1F200, 0x1F320, 2, + 0x1F321, 0x1F32C, 1, + 0x1F32D, 0x1F335, 2, + 0x1F336, 0x1F336, 1, + 0x1F337, 0x1F37C, 2, + 0x1F37D, 0x1F37D, 1, + 0x1F37E, 0x1F393, 2, + 0x1F394, 0x1F39F, 1, + 0x1F3A0, 0x1F3CA, 2, + 0x1F3CB, 0x1F3CE, 1, + 0x1F3CF, 0x1F3D3, 2, + 0x1F3D4, 0x1F3DF, 1, + 0x1F3E0, 0x1F3F0, 2, + 0x1F3F1, 0x1F3F3, 1, + 0x1F3F4, 0x1F3F4, 2, + 0x1F3F5, 0x1F3F7, 1, + 0x1F3F8, 0x1F43E, 2, + 0x1F43F, 0x1F43F, 1, + 0x1F440, 0x1F440, 2, + 0x1F441, 0x1F441, 1, + 0x1F442, 0x1F4FC, 2, + 0x1F4FD, 0x1F4FE, 1, + 0x1F4FF, 0x1F53D, 2, + 0x1F53E, 0x1F54A, 1, + 0x1F54B, 0x1F54E, 2, + 0x1F54F, 0x1F54F, 1, + 0x1F550, 0x1F567, 2, + 0x1F568, 0x1F579, 1, + 0x1F57A, 0x1F57A, 2, + 0x1F57B, 0x1F594, 1, + 0x1F595, 0x1F596, 2, + 0x1F597, 0x1F5A3, 1, + 0x1F5A4, 0x1F5A4, 2, + 0x1F5A5, 0x1F5FA, 1, + 0x1F5FB, 0x1F64F, 2, + 0x1F650, 0x1F67F, 1, + 0x1F680, 0x1F6C5, 2, + 0x1F6C6, 0x1F6CB, 1, + 0x1F6CC, 0x1F6CC, 2, + 0x1F6CD, 0x1F6CF, 1, + 0x1F6D0, 0x1F6D2, 2, + 0x1F6D3, 0x1F6D4, 1, + 0x1F6D5, 0x1F6DF, 2, + 0x1F6E0, 0x1F6EA, 1, + 0x1F6EB, 0x1F6EC, 2, + 0x1F6F0, 0x1F6F3, 1, + 0x1F6F4, 0x1F6FC, 2, + 0x1F700, 0x1F7D9, 1, + 0x1F7E0, 0x1F7F0, 2, + 0x1F800, 0x1F90B, 1, + 0x1F90C, 0x1F93A, 2, + 0x1F93B, 0x1F93B, 1, + 0x1F93C, 0x1F945, 2, + 0x1F946, 0x1F946, 1, + 0x1F947, 0x1F9FF, 2, + 0x1FA00, 0x1FA6D, 1, + 0x1FA70, 0x1FAF8, 2, + 0x1FB00, 0x1FBF9, 1, + 0x20000, 0x3FFFD, 2, + 0xE0001, 0x10FFFD, 1, +}; + +// +// End of Unicode 15.1.0 block. +// + +#ifdef __cplusplus +} +#endif + +#endif /* _UCG_TABLES_INCLUDED */ diff --git a/src/unicode.cpp b/src/unicode.cpp index c244a323c..665d5b182 100644 --- a/src/unicode.cpp +++ b/src/unicode.cpp @@ -162,3 +162,8 @@ end: if (codepoint_out) *codepoint_out = codepoint; return width; } + +// NOTE(Feoramund): It's down here because I made UCG use the utf8_decode above to avoid duplicating code. +extern "C" { +#include "ucg/ucg.c" +} diff --git a/tests/benchmark/all.odin b/tests/benchmark/all.odin new file mode 100644 index 000000000..d1b7662e2 --- /dev/null +++ b/tests/benchmark/all.odin @@ -0,0 +1,4 @@ +package benchmarks + +@(require) import "crypto" +@(require) import "hash" diff --git a/tests/benchmark/crypto/benchmark_crypto.odin b/tests/benchmark/crypto/benchmark_crypto.odin new file mode 100644 index 000000000..b2ac4bca3 --- /dev/null +++ b/tests/benchmark/crypto/benchmark_crypto.odin @@ -0,0 +1,412 @@ +package benchmark_core_crypto + +import "base:runtime" +import "core:encoding/hex" +import "core:fmt" +import "core:log" +import "core:strings" +import "core:testing" +import "core:time" + +import "core:crypto/aes" +import "core:crypto/chacha20" +import "core:crypto/chacha20poly1305" +import "core:crypto/ed25519" +import "core:crypto/poly1305" +import "core:crypto/x25519" + +// Cryptographic primitive benchmarks. + +@(test) +benchmark_crypto :: proc(t: ^testing.T) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + str: strings.Builder + strings.builder_init(&str, context.allocator) + defer { + log.info(strings.to_string(str)) + strings.builder_destroy(&str) + } + + { + name := "AES256-CTR 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_aes256_ctr, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "AES256-CTR 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "AES256-CTR 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } + { + name := "ChaCha20 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_chacha20, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "ChaCha20 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "ChaCha20 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } + { + name := "Poly1305 64 zero bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_poly1305, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "Poly1305 1024 zero bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } + { + name := "chacha20poly1305 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_chacha20poly1305, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "chacha20poly1305 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "chacha20poly1305 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } + { + name := "AES256-GCM 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_aes256_gcm, + teardown = _teardown_sized_buf, + } + + key := [aes.KEY_SIZE_256]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + ctx: aes.Context_GCM + aes.init_gcm(&ctx, key[:]) + + context.user_ptr = &ctx + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "AES256-GCM 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "AES256-GCM 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } + { + iters :: 10000 + + priv_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" + priv_bytes, _ := hex.decode(transmute([]byte)(priv_str), context.temp_allocator) + priv_key: ed25519.Private_Key + start := time.now() + for i := 0; i < iters; i = i + 1 { + ok := ed25519.private_key_set_bytes(&priv_key, priv_bytes) + assert(ok, "private key should deserialize") + } + elapsed := time.since(start) + fmt.sbprintfln(&str, + "ed25519.private_key_set_bytes: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + + pub_bytes := priv_key._pub_key._b[:] // "I know what I am doing" + pub_key: ed25519.Public_Key + start = time.now() + for i := 0; i < iters; i = i + 1 { + ok := ed25519.public_key_set_bytes(&pub_key, pub_bytes[:]) + assert(ok, "public key should deserialize") + } + elapsed = time.since(start) + fmt.sbprintfln(&str, + "ed25519.public_key_set_bytes: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + + msg := "Got a job for you, 621." + sig_bytes: [ed25519.SIGNATURE_SIZE]byte + msg_bytes := transmute([]byte)(msg) + start = time.now() + for i := 0; i < iters; i = i + 1 { + ed25519.sign(&priv_key, msg_bytes, sig_bytes[:]) + } + elapsed = time.since(start) + fmt.sbprintfln(&str, + "ed25519.sign: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + + start = time.now() + for i := 0; i < iters; i = i + 1 { + ok := ed25519.verify(&pub_key, msg_bytes, sig_bytes[:]) + assert(ok, "signature should validate") + } + elapsed = time.since(start) + fmt.sbprintfln(&str, + "ed25519.verify: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + } + { + point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" + + point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator) + scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator) + out: [x25519.POINT_SIZE]byte = --- + + iters :: 10000 + start := time.now() + for i := 0; i < iters; i = i + 1 { + x25519.scalarmult(out[:], scalar[:], point[:]) + } + elapsed := time.since(start) + + fmt.sbprintfln(&str, + "x25519.scalarmult: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + } +} + +@(private) +_setup_sized_buf :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + assert(options != nil) + + options.input = make([]u8, options.bytes, allocator) + return nil if len(options.input) == options.bytes else .Allocation_Error +} + +@(private) +_teardown_sized_buf :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + assert(options != nil) + + delete(options.input) + return nil +} + +@(private) +_benchmark_chacha20 :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [chacha20.KEY_SIZE]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + nonce := [chacha20.NONCE_SIZE]byte { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + } + + ctx: chacha20.Context = --- + chacha20.init(&ctx, key[:], nonce[:]) + + for _ in 0 ..= options.rounds { + chacha20.xor_bytes(&ctx, buf, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + +@(private) +_benchmark_poly1305 :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [poly1305.KEY_SIZE]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + + tag: [poly1305.TAG_SIZE]byte = --- + for _ in 0 ..= options.rounds { + poly1305.sum(tag[:], buf, key[:]) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + //options.hash = u128(h) + return nil +} + +@(private) +_benchmark_chacha20poly1305 :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [chacha20.KEY_SIZE]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + nonce := [chacha20.NONCE_SIZE]byte { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + } + + tag: [chacha20poly1305.TAG_SIZE]byte = --- + + for _ in 0 ..= options.rounds { + chacha20poly1305.encrypt(buf, tag[:], key[:], nonce[:], nil, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + +@(private) +_benchmark_aes256_ctr :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [aes.KEY_SIZE_256]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + nonce := [aes.CTR_IV_SIZE]byte { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + + ctx: aes.Context_CTR = --- + aes.init_ctr(&ctx, key[:], nonce[:]) + + for _ in 0 ..= options.rounds { + aes.xor_bytes_ctr(&ctx, buf, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + +_benchmark_aes256_gcm :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + nonce: [aes.GCM_NONCE_SIZE]byte + tag: [aes.GCM_TAG_SIZE]byte = --- + + ctx := transmute(^aes.Context_GCM)context.user_ptr + + for _ in 0 ..= options.rounds { + aes.seal_gcm(ctx, buf, tag[:], nonce[:], nil, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + +@(private) +benchmark_print :: proc(str: ^strings.Builder, name: string, options: ^time.Benchmark_Options, loc := #caller_location) { + fmt.sbprintfln(str, "[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", + name, + options.rounds, + options.processed, + time.duration_nanoseconds(options.duration), + options.rounds_per_second, + options.megabytes_per_second, + ) +} diff --git a/tests/benchmark/hash/benchmark_hash.odin b/tests/benchmark/hash/benchmark_hash.odin new file mode 100644 index 000000000..84eb827e7 --- /dev/null +++ b/tests/benchmark/hash/benchmark_hash.odin @@ -0,0 +1,218 @@ +package benchmark_core_hash + +import "core:fmt" +import "core:hash/xxhash" +import "base:intrinsics" +import "core:strings" +import "core:testing" +import "core:time" + +@(test) +benchmark_hash :: proc(t: ^testing.T) { + str: strings.Builder + strings.builder_init(&str, context.allocator) + defer { + fmt.println(strings.to_string(str)) + strings.builder_destroy(&str) + } + + { + name := "XXH32 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh32, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x85f6413c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH32 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh32, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x9430f97f) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH64 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x17bb1103c92c502f) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH64 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x87d2a1b6e1163ef1) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH3_64 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh3_64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x801fedc74ccd608c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH3_64 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh3_64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x918780b90550bf34) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH3_128 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh3_128, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x6ba30a4e9dffe1ff801fedc74ccd608c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH3_128 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh3_128, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0xb6ef17a3448492b6918780b90550bf34) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } +} + +// Benchmarks + +setup_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + assert(options != nil) + + options.input = make([]u8, options.bytes, allocator) + return nil if len(options.input) == options.bytes else .Allocation_Error +} + +teardown_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + assert(options != nil) + + delete(options.input) + return nil +} + +benchmark_xxh32 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + buf := options.input + + h: u32 + for _ in 0..=options.rounds { + h = xxhash.XXH32(buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + options.hash = u128(h) + return nil +} + +benchmark_xxh64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + buf := options.input + + h: u64 + for _ in 0..=options.rounds { + h = xxhash.XXH64(buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + options.hash = u128(h) + return nil +} + +benchmark_xxh3_64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + buf := options.input + + h: u64 + for _ in 0..=options.rounds { + h = xxhash.XXH3_64(buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + options.hash = u128(h) + return nil +} + +benchmark_xxh3_128 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + buf := options.input + + h: u128 + for _ in 0..=options.rounds { + h = xxhash.XXH3_128(buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + options.hash = h + return nil +} + +benchmark_print :: proc(str: ^strings.Builder, name: string, options: ^time.Benchmark_Options, loc := #caller_location) { + fmt.sbprintfln(str, "[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", + name, + options.rounds, + options.processed, + time.duration_nanoseconds(options.duration), + options.rounds_per_second, + options.megabytes_per_second, + ) +} \ No newline at end of file diff --git a/tests/common/common.odin b/tests/common/common.odin deleted file mode 100644 index 021fb21c5..000000000 --- a/tests/common/common.odin +++ /dev/null @@ -1,81 +0,0 @@ -// Boilerplate for tests -package common - -import "core:testing" -import "core:fmt" -import "core:os" -import "core:strings" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log - errorf :: testing.errorf -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v:%s] FAIL %v\n", loc, loc.procedure, message) - return - } - } - errorf :: proc(t: ^testing.T, message: string, args: ..any, loc := #caller_location) { - TEST_fail += 1 - fmt.printf("[%v:%s] Error %v\n", loc, loc.procedure, fmt.tprintf(message, ..args)) - return - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -report :: proc(t: ^testing.T) { - if TEST_fail > 0 { - if TEST_fail > 1 { - fmt.printf("%v/%v tests successful, %v tests failed.\n", TEST_count - TEST_fail, TEST_count, TEST_fail) - } else { - fmt.printf("%v/%v tests successful, 1 test failed.\n", TEST_count - TEST_fail, TEST_count) - } - os.exit(1) - } else { - fmt.printf("%v/%v tests successful.\n", TEST_count, TEST_count) - } -} - -// Returns absolute path to `sub_path` where `sub_path` is within the "tests/" sub-directory of the Odin project root -// and we're being run from the Odin project root or from a sub-directory of "tests/" -// e.g. get_data_path("assets/blah") will return "/Odin_root/tests/assets/blah" if run within "/Odin_root", -// "/Odin_root/tests" or "/Odin_root/tests/subdir" etc -get_data_path :: proc(t: ^testing.T, sub_path: string) -> (data_path: string) { - - cwd := os.get_current_directory() - defer delete(cwd) - - when ODIN_OS == .Windows { - norm, was_allocation := strings.replace_all(cwd, "\\", "/") - if !was_allocation { - norm = strings.clone(norm) - } - defer delete(norm) - } else { - norm := cwd - } - - last_index := strings.last_index(norm, "/tests/") - if last_index == -1 { - len := len(norm) - if len >= 6 && norm[len-6:] == "/tests" { - data_path = fmt.tprintf("%s/%s", norm, sub_path) - } else { - data_path = fmt.tprintf("%s/tests/%s", norm, sub_path) - } - } else { - data_path = fmt.tprintf("%s/tests/%s", norm[:last_index], sub_path) - } - - return data_path -} diff --git a/tests/core/.gitignore b/tests/core/.gitignore index cd013e188..2a4e21679 100644 --- a/tests/core/.gitignore +++ b/tests/core/.gitignore @@ -1,3 +1,4 @@ +*.bmp *.zip *.png math_big_test_library.* \ No newline at end of file diff --git a/tests/core/Makefile b/tests/core/Makefile deleted file mode 100644 index 12047f0ff..000000000 --- a/tests/core/Makefile +++ /dev/null @@ -1,103 +0,0 @@ -ODIN=../../odin -PYTHON=$(shell which python3) -COMMON=-vet -strict-style -COLLECTION=-collection:tests=.. - -all: c_libc_test \ - compress_test \ - container_test \ - crypto_test \ - download_test_assets \ - encoding_test \ - filepath_test \ - fmt_test \ - hash_test \ - i18n_test \ - image_test \ - linalg_glsl_math_test \ - match_test \ - math_test \ - net_test \ - noise_test \ - os_exit_test \ - reflect_test \ - slice_test \ - strings_test \ - thread_test \ - runtime_test \ - time_test - -download_test_assets: - $(PYTHON) download_assets.py - -image_test: - $(ODIN) run image $(COMMON) -out:test_core_image - -compress_test: - $(ODIN) run compress $(COMMON) -out:test_core_compress - -container_test: - $(ODIN) run container $(COMMON) $(COLLECTION) -out:test_core_container - -strings_test: - $(ODIN) run strings $(COMMON) -out:test_core_strings - -hash_test: - $(ODIN) run hash $(COMMON) -o:speed -no-bounds-check -out:test_hash - -crypto_test: - $(ODIN) run crypto $(COMMON) $(COLLECTION) -o:speed -no-bounds-check -out:test_crypto - -noise_test: - $(ODIN) run math/noise $(COMMON) -out:test_noise - -encoding_test: - $(ODIN) run encoding/hxa $(COMMON) $(COLLECTION) -out:test_hxa - $(ODIN) run encoding/json $(COMMON) -out:test_json - $(ODIN) run encoding/varint $(COMMON) -out:test_varint - $(ODIN) run encoding/xml $(COMMON) -out:test_xml - $(ODIN) run encoding/cbor $(COMMON) -out:test_cbor - $(ODIN) run encoding/hex $(COMMON) -out:test_hex - $(ODIN) run encoding/base64 $(COMMON) -out:test_base64 - -math_test: - $(ODIN) run math $(COMMON) $(COLLECTION) -out:test_core_math - -linalg_glsl_math_test: - $(ODIN) run math/linalg/glsl $(COMMON) $(COLLECTION) -out:test_linalg_glsl_math - -filepath_test: - $(ODIN) run path/filepath $(COMMON) $(COLLECTION) -out:test_core_filepath - -reflect_test: - $(ODIN) run reflect $(COMMON) $(COLLECTION) -out:test_core_reflect - -slice_test: - $(ODIN) run slice $(COMMON) -out:test_core_slice - -os_exit_test: - $(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0 - -i18n_test: - $(ODIN) run text/i18n $(COMMON) -out:test_core_i18n - -match_test: - $(ODIN) run text/match $(COMMON) -out:test_core_match - -c_libc_test: - $(ODIN) run c/libc $(COMMON) -out:test_core_libc - -net_test: - $(ODIN) run net $(COMMON) -out:test_core_net - -fmt_test: - $(ODIN) run fmt $(COMMON) -out:test_core_fmt - -thread_test: - $(ODIN) run thread $(COMMON) -out:test_core_thread - -runtime_test: - $(ODIN) run runtime $(COMMON) -out:test_core_runtime - -time_test: - $(ODIN) run time $(COMMON) -out:test_core_time diff --git a/tests/core/assets/I18N/mixed_context.mo b/tests/core/assets/I18N/mixed_context.mo new file mode 100644 index 000000000..53efee09a Binary files /dev/null and b/tests/core/assets/I18N/mixed_context.mo differ diff --git a/tests/core/assets/I18N/mixed_context.po b/tests/core/assets/I18N/mixed_context.po new file mode 100644 index 000000000..666e0a8d3 --- /dev/null +++ b/tests/core/assets/I18N/mixed_context.po @@ -0,0 +1,18 @@ +msgid "" +msgstr "" +"Project-Id-Version: 0.1.0\n" +"PO-Revision-Date: 2024-04-13 11:13+0200\n" +"Last-Translator: Someone \n" +"Language-Team: English\n" +"Language: en_IE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgctxt "Context" +msgid "Message1" +msgstr "This is message 1 with Context" + +msgid "Message1" +msgstr "This is message 1 without Context" diff --git a/tests/core/assets/I18N/plur.mo b/tests/core/assets/I18N/plur.mo new file mode 100644 index 000000000..03243c0f6 Binary files /dev/null and b/tests/core/assets/I18N/plur.mo differ diff --git a/tests/core/assets/I18N/plur.po b/tests/core/assets/I18N/plur.po new file mode 100644 index 000000000..45c8209ca --- /dev/null +++ b/tests/core/assets/I18N/plur.po @@ -0,0 +1,17 @@ +msgid "" +msgstr "" +"Project-Id-Version: 0.1.0\n" +"PO-Revision-Date: 2024-04-13 11:13+0200\n" +"Last-Translator: Someone \n" +"Language-Team: English\n" +"Language: it_IT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2);\n" + +msgid "Message1" +msgid_plural "Message1/plural" +msgstr[0] "This is message 1" +msgstr[1] "This is message 1 - plural A" +msgstr[2] "This is message 1 - plural B" diff --git a/tests/core/assets/XML/attribute-whitespace.xml b/tests/core/assets/XML/attribute-whitespace.xml new file mode 100644 index 000000000..6381225d5 --- /dev/null +++ b/tests/core/assets/XML/attribute-whitespace.xml @@ -0,0 +1,8 @@ + + +Barzle +<부끄러운:barzle> + Indeed! + \ No newline at end of file diff --git a/tests/core/build.bat b/tests/core/build.bat deleted file mode 100644 index 36d2bb9c4..000000000 --- a/tests/core/build.bat +++ /dev/null @@ -1,110 +0,0 @@ -@echo off -set COMMON=-no-bounds-check -vet -strict-style -set COLLECTION=-collection:tests=.. -set PATH_TO_ODIN==..\..\odin -python3 download_assets.py -echo --- -echo Running core:image tests -echo --- -%PATH_TO_ODIN% run image %COMMON% -out:test_core_image.exe || exit /b - -echo --- -echo Running core:compress tests -echo --- -%PATH_TO_ODIN% run compress %COMMON% -out:test_core_compress.exe || exit /b - -echo --- -echo Running core:strings tests -echo --- -%PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe || exit /b - -echo --- -echo Running core:hash tests -echo --- -%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_core_hash.exe || exit /b - -echo --- -echo Running core:odin tests -echo --- -%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe || exit /b - -echo --- -echo Running core:crypto tests -echo --- -%PATH_TO_ODIN% run crypto %COMMON% %COLLECTION% -out:test_crypto.exe || exit /b - -echo --- -echo Running core:encoding tests -echo --- -rem %PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe || exit /b -%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b -%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b -%PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b -%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe || exit /b -%PATH_TO_ODIN% run encoding/hex %COMMON% -out:test_hex.exe || exit /b -%PATH_TO_ODIN% run encoding/base64 %COMMON% -out:test_base64.exe || exit /b - -echo --- -echo Running core:math/noise tests -echo --- -%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise.exe || exit /b - -echo --- -echo Running core:math tests -echo --- -%PATH_TO_ODIN% run math %COMMON% %COLLECTION% -out:test_core_math.exe || exit /b - -echo --- -echo Running core:math/linalg/glsl tests -echo --- -%PATH_TO_ODIN% run math/linalg/glsl %COMMON% %COLLECTION% -out:test_linalg_glsl.exe || exit /b - -echo --- -echo Running core:path/filepath tests -echo --- -%PATH_TO_ODIN% run path/filepath %COMMON% %COLLECTION% -out:test_core_filepath.exe || exit /b - -echo --- -echo Running core:reflect tests -echo --- -%PATH_TO_ODIN% run reflect %COMMON% %COLLECTION% -out:test_core_reflect.exe || exit /b - -echo --- -echo Running core:slice tests -echo --- -%PATH_TO_ODIN% run slice %COMMON% -out:test_core_slice.exe || exit /b - -echo --- -echo Running core:text/i18n tests -echo --- -%PATH_TO_ODIN% run text\i18n %COMMON% -out:test_core_i18n.exe || exit /b - -echo --- -echo Running core:net -echo --- -%PATH_TO_ODIN% run net %COMMON% -out:test_core_net.exe || exit /b - -echo --- -echo Running core:slice tests -echo --- -%PATH_TO_ODIN% run slice %COMMON% -out:test_core_slice.exe || exit /b - -echo --- -echo Running core:container tests -echo --- -%PATH_TO_ODIN% run container %COMMON% %COLLECTION% -out:test_core_container.exe || exit /b - -echo --- -echo Running core:thread tests -echo --- -%PATH_TO_ODIN% run thread %COMMON% %COLLECTION% -out:test_core_thread.exe || exit /b - -echo --- -echo Running core:runtime tests -echo --- -%PATH_TO_ODIN% run runtime %COMMON% %COLLECTION% -out:test_core_runtime.exe || exit /b - -echo --- -echo Running core:time tests -echo --- -%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b \ No newline at end of file diff --git a/tests/core/c/libc/test_core_libc.odin b/tests/core/c/libc/test_core_libc.odin deleted file mode 100644 index 9b5014dee..000000000 --- a/tests/core/c/libc/test_core_libc.odin +++ /dev/null @@ -1,36 +0,0 @@ -package test_core_libc - -import "core:fmt" -import "core:os" -import "core:testing" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_libc_complex(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} diff --git a/tests/core/c/libc/test_core_libc_complex_pow.odin b/tests/core/c/libc/test_core_libc_complex_pow.odin index 90928794c..cd50c8f6a 100644 --- a/tests/core/c/libc/test_core_libc_complex_pow.odin +++ b/tests/core/c/libc/test_core_libc_complex_pow.odin @@ -1,8 +1,8 @@ package test_core_libc import "core:testing" -import "core:fmt" import "core:c/libc" +import "core:log" reldiff :: proc(lhs, rhs: $T) -> f64 { if lhs == rhs { @@ -14,7 +14,7 @@ reldiff :: proc(lhs, rhs: $T) -> f64 { return out } -isclose :: proc(lhs, rhs: $T, rtol:f64 = 1e-12, atol:f64 = 1e-12) -> bool { +isclose :: proc(t: ^testing.T, lhs, rhs: $T, rtol:f64 = 1e-12, atol:f64 = 1e-12) -> bool { adiff := f64(abs(lhs - rhs)) if adiff < atol { return true @@ -23,7 +23,7 @@ isclose :: proc(lhs, rhs: $T, rtol:f64 = 1e-12, atol:f64 = 1e-12) -> bool { if rdiff < rtol { return true } - fmt.printf("not close -- lhs:%v rhs:%v -- adiff:%e rdiff:%e\n",lhs, rhs, adiff, rdiff) + log.infof("not close -- lhs:%v rhs:%v -- adiff:%e rdiff:%e\n",lhs, rhs, adiff, rdiff) return false } @@ -44,7 +44,6 @@ test_libc_complex :: proc(t: ^testing.T) { test_libc_pow_binding(t, libc.complex_float, f32, libc_powf, 1e-12, 1e-5) } -@test test_libc_pow_binding :: proc(t: ^testing.T, $LIBC_COMPLEX:typeid, $F:typeid, pow: proc(LIBC_COMPLEX, LIBC_COMPLEX) -> LIBC_COMPLEX, rtol: f64, atol: f64) { // Tests that c/libc/pow(f) functions have two arguments and that the function works as expected for simple inputs @@ -56,8 +55,8 @@ test_libc_pow_binding :: proc(t: ^testing.T, $LIBC_COMPLEX:typeid, $F:typeid, po for n in -4..=4 { complex_power := LIBC_COMPLEX(complex(F(n), F(0.))) result := pow(complex_base, complex_power) - expect(t, isclose(expected_real, F(real(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol)) - expect(t, isclose(expected_imag, F(imag(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol)) + testing.expectf(t, isclose(t, expected_real, F(real(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol) + testing.expectf(t, isclose(t, expected_imag, F(imag(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol) expected_real *= 2 } } @@ -83,8 +82,8 @@ test_libc_pow_binding :: proc(t: ^testing.T, $LIBC_COMPLEX:typeid, $F:typeid, po expected_real = 0. expected_imag = -value } - expect(t, isclose(expected_real, F(real(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol)) - expect(t, isclose(expected_imag, F(imag(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol)) + testing.expectf(t, isclose(t, expected_real, F(real(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol) + testing.expectf(t, isclose(t, expected_imag, F(imag(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol) value *= 2 } } diff --git a/tests/core/compress/test_core_compress.odin b/tests/core/compress/test_core_compress.odin index ac7555e9a..4ab63ae67 100644 --- a/tests/core/compress/test_core_compress.odin +++ b/tests/core/compress/test_core_compress.odin @@ -15,47 +15,7 @@ import "core:testing" import "core:compress/zlib" import "core:compress/gzip" import "core:compress/shoco" - import "core:bytes" -import "core:fmt" - -import "core:mem" -import "core:os" -import "core:io" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - w := io.to_writer(os.stream_from_handle(os.stdout)) - t := testing.T{w=w} - zlib_test(&t) - gzip_test(&t) - shoco_test(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} @test zlib_test :: proc(t: ^testing.T) { @@ -80,26 +40,14 @@ zlib_test :: proc(t: ^testing.T) { } buf: bytes.Buffer + err := zlib.inflate(ODIN_DEMO, &buf) - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - err := zlib.inflate(ODIN_DEMO, &buf) - - expect(t, err == nil, "ZLIB failed to decompress ODIN_DEMO") + testing.expect(t, err == nil, "ZLIB failed to decompress ODIN_DEMO") s := bytes.buffer_to_string(&buf) - expect(t, s[68] == 240 && s[69] == 159 && s[70] == 152, "ZLIB result should've contained 😃 at position 68.") - - expect(t, len(s) == 438, "ZLIB result has an unexpected length.") - + testing.expect(t, s[68] == 240 && s[69] == 159 && s[70] == 152, "ZLIB result should've contained 😃 at position 68.") + testing.expect(t, len(s) == 438, "ZLIB result has an unexpected length.") bytes.buffer_destroy(&buf) - - for _, v in track.allocation_map { - error := fmt.tprintf("ZLIB test leaked %v bytes", v.size) - expect(t, false, error) - } } @test @@ -117,24 +65,12 @@ gzip_test :: proc(t: ^testing.T) { } buf: bytes.Buffer + err := gzip.load(TEST, &buf) - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - err := gzip.load(TEST, &buf) // , 438); - - expect(t, err == nil, "GZIP failed to decompress TEST") - s := bytes.buffer_to_string(&buf) - - expect(t, s == "payload", "GZIP result wasn't 'payload'") + testing.expect(t, err == nil, "GZIP failed to decompress TEST") + testing.expect(t, bytes.buffer_to_string(&buf) == "payload", "GZIP result wasn't 'payload'") bytes.buffer_destroy(&buf) - - for _, v in track.allocation_map { - error := fmt.tprintf("GZIP test leaked %v bytes", v.size) - expect(t, false, error) - } } @test @@ -168,31 +104,26 @@ shoco_test :: proc(t: ^testing.T) { defer delete(buffer) size, err := shoco.decompress(v.compressed, buffer[:]) - msg := fmt.tprintf("Expected `decompress` to return `nil`, got %v", err) - expect(t, err == nil, msg) + testing.expectf(t, err == nil, "Expected `decompress` to return `nil`, got %v", err) - msg = fmt.tprintf("Decompressed %v bytes into %v. Expected to decompress into %v bytes.", len(v.compressed), size, expected_raw) - expect(t, size == expected_raw, msg) - expect(t, string(buffer[:size]) == string(v.raw), "Decompressed contents don't match.") + testing.expectf(t, size == expected_raw, "Decompressed %v bytes into %v. Expected to decompress into %v bytes", len(v.compressed), size, expected_raw) + testing.expect(t, string(buffer[:size]) == string(v.raw), "Decompressed contents don't match") size, err = shoco.compress(string(v.raw), buffer[:]) - expect(t, err == nil, "Expected `compress` to return `nil`.") + testing.expect(t, err == nil, "Expected `compress` to return `nil`.") - msg = fmt.tprintf("Compressed %v bytes into %v. Expected to compress into %v bytes.", expected_raw, size, expected_compressed) - expect(t, size == expected_compressed, msg) + testing.expectf(t, size == expected_compressed, "Compressed %v bytes into %v. Expected to compress into %v bytes", expected_raw, size, expected_compressed) size, err = shoco.decompress(v.compressed, buffer[:expected_raw - 10]) - msg = fmt.tprintf("Decompressing into too small a buffer returned %v, expected `.Output_Too_Short`", err) - expect(t, err == .Output_Too_Short, msg) + testing.expectf(t, err == .Output_Too_Short, "Decompressing into too small a buffer returned %v, expected `.Output_Too_Short`", err) size, err = shoco.compress(string(v.raw), buffer[:expected_compressed - 10]) - msg = fmt.tprintf("Compressing into too small a buffer returned %v, expected `.Output_Too_Short`", err) - expect(t, err == .Output_Too_Short, msg) + testing.expectf(t, err == .Output_Too_Short, "Compressing into too small a buffer returned %v, expected `.Output_Too_Short`", err) size, err = shoco.decompress(v.compressed[:v.short_pack], buffer[:]) - expect(t, err == .Stream_Too_Short, "Expected `decompress` to return `Stream_Too_Short` because there was no more data after selecting a pack.") + testing.expectf(t, err == .Stream_Too_Short, "Insufficient data after pack returned %v, expected `.Stream_Too_Short`", err) size, err = shoco.decompress(v.compressed[:v.short_sentinel], buffer[:]) - expect(t, err == .Stream_Too_Short, "Expected `decompress` to return `Stream_Too_Short` because there was no more data after non-ASCII sentinel.") + testing.expectf(t, err == .Stream_Too_Short, "No more data after non-ASCII sentinel returned %v, expected `.Stream_Too_Short`", err) } -} \ No newline at end of file +} diff --git a/tests/core/container/test_core_avl.odin b/tests/core/container/test_core_avl.odin index f6343c5ea..0e2d0d94a 100644 --- a/tests/core/container/test_core_avl.odin +++ b/tests/core/container/test_core_avl.odin @@ -4,50 +4,51 @@ import "core:container/avl" import "core:math/rand" import "core:slice" import "core:testing" - -import tc "tests:common" +import "core:log" @(test) test_avl :: proc(t: ^testing.T) { - tc.log(t, "Testing avl") + log.infof("Testing avl using random seed %v.", t.seed) // Initialization. tree: avl.Tree(int) avl.init(&tree, slice.cmp_proc(int)) - tc.expect(t, avl.len(&tree) == 0, "empty: len should be 0") - tc.expect(t, avl.first(&tree) == nil, "empty: first should be nil") - tc.expect(t, avl.last(&tree) == nil, "empty: last should be nil") + testing.expect(t, avl.len(&tree) == 0, "empty: len should be 0") + testing.expect(t, avl.first(&tree) == nil, "empty: first should be nil") + testing.expect(t, avl.last(&tree) == nil, "empty: last should be nil") iter := avl.iterator(&tree, avl.Direction.Forward) - tc.expect(t, avl.iterator_get(&iter) == nil, "empty/iterator: first node should be nil") + testing.expect(t, avl.iterator_get(&iter) == nil, "empty/iterator: first node should be nil") // Test insertion. NR_INSERTS :: 32 + 1 // Ensure at least 1 collision. inserted_map := make(map[int]^avl.Node(int)) + defer delete(inserted_map) for i := 0; i < NR_INSERTS; i += 1 { v := int(rand.uint32() & 0x1f) existing_node, in_map := inserted_map[v] n, ok, _ := avl.find_or_insert(&tree, v) - tc.expect(t, in_map != ok, "insert: ok should match inverse of map lookup") + testing.expect(t, in_map != ok, "insert: ok should match inverse of map lookup") if ok { inserted_map[v] = n } else { - tc.expect(t, existing_node == n, "insert: expecting existing node") + testing.expect(t, existing_node == n, "insert: expecting existing node") } } nrEntries := len(inserted_map) - tc.expect(t, avl.len(&tree) == nrEntries, "insert: len after") - tree_validate(t, &tree) + testing.expect(t, avl.len(&tree) == nrEntries, "insert: len after") + validate_avl(t, &tree) // Ensure that all entries can be found. for k, v in inserted_map { - tc.expect(t, v == avl.find(&tree, k), "Find(): Node") - tc.expect(t, k == v.value, "Find(): Node value") + testing.expect(t, v == avl.find(&tree, k), "Find(): Node") + testing.expect(t, k == v.value, "Find(): Node value") } // Test the forward/backward iterators. inserted_values: [dynamic]int + defer delete(inserted_values) for k in inserted_map { append(&inserted_values, k) } @@ -57,38 +58,38 @@ test_avl :: proc(t: ^testing.T) { visited: int for node in avl.iterator_next(&iter) { v, idx := node.value, visited - tc.expect(t, inserted_values[idx] == v, "iterator/forward: value") - tc.expect(t, node == avl.iterator_get(&iter), "iterator/forward: get") + testing.expect(t, inserted_values[idx] == v, "iterator/forward: value") + testing.expect(t, node == avl.iterator_get(&iter), "iterator/forward: get") visited += 1 } - tc.expect(t, visited == nrEntries, "iterator/forward: visited") + testing.expect(t, visited == nrEntries, "iterator/forward: visited") slice.reverse(inserted_values[:]) iter = avl.iterator(&tree, avl.Direction.Backward) visited = 0 for node in avl.iterator_next(&iter) { v, idx := node.value, visited - tc.expect(t, inserted_values[idx] == v, "iterator/backward: value") + testing.expect(t, inserted_values[idx] == v, "iterator/backward: value") visited += 1 } - tc.expect(t, visited == nrEntries, "iterator/backward: visited") + testing.expect(t, visited == nrEntries, "iterator/backward: visited") // Test removal. rand.shuffle(inserted_values[:]) for v, i in inserted_values { node := avl.find(&tree, v) - tc.expect(t, node != nil, "remove: find (pre)") + testing.expect(t, node != nil, "remove: find (pre)") ok := avl.remove(&tree, v) - tc.expect(t, ok, "remove: succeeds") - tc.expect(t, nrEntries - (i + 1) == avl.len(&tree), "remove: len (post)") - tree_validate(t, &tree) + testing.expect(t, ok, "remove: succeeds") + testing.expect(t, nrEntries - (i + 1) == avl.len(&tree), "remove: len (post)") + validate_avl(t, &tree) - tc.expect(t, nil == avl.find(&tree, v), "remove: find (post") + testing.expect(t, nil == avl.find(&tree, v), "remove: find (post") } - tc.expect(t, avl.len(&tree) == 0, "remove: len should be 0") - tc.expect(t, avl.first(&tree) == nil, "remove: first should be nil") - tc.expect(t, avl.last(&tree) == nil, "remove: last should be nil") + testing.expect(t, avl.len(&tree) == 0, "remove: len should be 0") + testing.expect(t, avl.first(&tree) == nil, "remove: first should be nil") + testing.expect(t, avl.last(&tree) == nil, "remove: last should be nil") // Refill the tree. for v in inserted_values { @@ -101,29 +102,29 @@ test_avl :: proc(t: ^testing.T) { v := node.value ok := avl.iterator_remove(&iter) - tc.expect(t, ok, "iterator/remove: success") + testing.expect(t, ok, "iterator/remove: success") ok = avl.iterator_remove(&iter) - tc.expect(t, !ok, "iterator/remove: redundant removes should fail") + testing.expect(t, !ok, "iterator/remove: redundant removes should fail") - tc.expect(t, avl.find(&tree, v) == nil, "iterator/remove: node should be gone") - tc.expect(t, avl.iterator_get(&iter) == nil, "iterator/remove: get should return nil") + testing.expect(t, avl.find(&tree, v) == nil, "iterator/remove: node should be gone") + testing.expect(t, avl.iterator_get(&iter) == nil, "iterator/remove: get should return nil") // Ensure that iterator_next still works. node, ok = avl.iterator_next(&iter) - tc.expect(t, ok == (avl.len(&tree) > 0), "iterator/remove: next should return false") - tc.expect(t, node == avl.first(&tree), "iterator/remove: next should return first") + testing.expect(t, ok == (avl.len(&tree) > 0), "iterator/remove: next should return false") + testing.expect(t, node == avl.first(&tree), "iterator/remove: next should return first") - tree_validate(t, &tree) + validate_avl(t, &tree) } - tc.expect(t, avl.len(&tree) == nrEntries - 1, "iterator/remove: len should drop by 1") + testing.expect(t, avl.len(&tree) == nrEntries - 1, "iterator/remove: len should drop by 1") avl.destroy(&tree) - tc.expect(t, avl.len(&tree) == 0, "destroy: len should be 0") + testing.expect(t, avl.len(&tree) == 0, "destroy: len should be 0") } @(private) -tree_validate :: proc(t: ^testing.T, tree: ^avl.Tree($Value)) { +validate_avl :: proc(t: ^testing.T, tree: ^avl.Tree($Value)) { tree_check_invariants(t, tree, tree._root, nil) } @@ -138,10 +139,10 @@ tree_check_invariants :: proc( } // Validate the parent pointer. - tc.expect(t, parent == node._parent, "invalid parent pointer") + testing.expect(t, parent == node._parent, "invalid parent pointer") // Validate that the balance factor is -1, 0, 1. - tc.expect( + testing.expect( t, node._balance == -1 || node._balance == 0 || node._balance == 1, "invalid balance factor", @@ -152,7 +153,7 @@ tree_check_invariants :: proc( r_height := tree_check_invariants(t, tree, node._right, node) // Validate the AVL invariant and the balance factor. - tc.expect(t, int(node._balance) == r_height - l_height, "AVL balance factor invariant violated") + testing.expect(t, int(node._balance) == r_height - l_height, "AVL balance factor invariant violated") if l_height > r_height { return l_height + 1 } diff --git a/tests/core/container/test_core_container.odin b/tests/core/container/test_core_container.odin deleted file mode 100644 index f816a6bcb..000000000 --- a/tests/core/container/test_core_container.odin +++ /dev/null @@ -1,26 +0,0 @@ -package test_core_container - -import "core:fmt" -import "core:testing" - -import tc "tests:common" - -expect_equal :: proc(t: ^testing.T, the_slice, expected: []int, loc := #caller_location) { - _eq :: proc(a, b: []int) -> bool { - if len(a) != len(b) do return false - for a, i in a { - if b[i] != a do return false - } - return true - } - tc.expect(t, _eq(the_slice, expected), fmt.tprintf("Expected %v, got %v\n", the_slice, expected), loc) -} - -main :: proc() { - t := testing.T{} - - test_avl(&t) - test_small_array(&t) - - tc.report(&t) -} diff --git a/tests/core/container/test_core_rbtree.odin b/tests/core/container/test_core_rbtree.odin new file mode 100644 index 000000000..b686ef6dd --- /dev/null +++ b/tests/core/container/test_core_rbtree.odin @@ -0,0 +1,237 @@ +package test_core_container + +import rb "core:container/rbtree" +import "core:math/rand" +import "core:testing" +import "base:intrinsics" +import "core:mem" +import "core:slice" +import "core:log" + +test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + defer mem.tracking_allocator_destroy(&track) + context.allocator = mem.tracking_allocator(&track) + + log.infof("Testing Red-Black Tree($Key=%v,$Value=%v) using random seed %v.", type_info_of(Key), type_info_of(Value), t.seed) + tree: rb.Tree(Key, Value) + rb.init(&tree) + + testing.expect(t, rb.len(&tree) == 0, "empty: len should be 0") + testing.expect(t, rb.first(&tree) == nil, "empty: first should be nil") + testing.expect(t, rb.last(&tree) == nil, "empty: last should be nil") + iter := rb.iterator(&tree, .Forward) + testing.expect(t, rb.iterator_get(&iter) == nil, "empty/iterator: first node should be nil") + + // Test insertion. + NR_INSERTS :: 32 + 1 // Ensure at least 1 collision. + inserted_map := make(map[Key]^rb.Node(Key, Value)) + + min_key := max(Key) + max_key := min(Key) + + for i := 0; i < NR_INSERTS; i += 1 { + k := Key(rand.uint32()) & 0x1f + min_key = min(min_key, k); max_key = max(max_key, k) + v := Value(rand.uint32()) + + existing_node, in_map := inserted_map[k] + n, inserted, _ := rb.find_or_insert(&tree, k, v) + testing.expect(t, in_map != inserted, "insert: inserted should match inverse of map lookup") + if inserted { + inserted_map[k] = n + } else { + testing.expect(t, existing_node == n, "insert: expecting existing node") + } + } + + entry_count := len(inserted_map) + testing.expect(t, rb.len(&tree) == entry_count, "insert: len after") + validate_rbtree(t, &tree) + + first := rb.first(&tree) + last := rb.last(&tree) + testing.expectf(t, first != nil && first.key == min_key, "insert: first should be present with key %v", min_key) + testing.expectf(t, last != nil && last.key == max_key, "insert: last should be present with key %v", max_key) + + // Ensure that all entries can be found. + for k, v in inserted_map { + testing.expect(t, v == rb.find(&tree, k), "Find(): Node") + testing.expect(t, k == v.key, "Find(): Node key") + } + + // Test the forward/backward iterators. + inserted_keys: [dynamic]Key + for k in inserted_map { + append(&inserted_keys, k) + } + slice.sort(inserted_keys[:]) + + iter = rb.iterator(&tree, rb.Direction.Forward) + visited: int + for node in rb.iterator_next(&iter) { + k, idx := node.key, visited + testing.expect(t, inserted_keys[idx] == k, "iterator/forward: key") + testing.expect(t, node == rb.iterator_get(&iter), "iterator/forward: get") + visited += 1 + } + testing.expect(t, visited == entry_count, "iterator/forward: visited") + + slice.reverse(inserted_keys[:]) + iter = rb.iterator(&tree, rb.Direction.Backward) + visited = 0 + for node in rb.iterator_next(&iter) { + k, idx := node.key, visited + testing.expect(t, inserted_keys[idx] == k, "iterator/backward: key") + visited += 1 + } + testing.expect(t, visited == entry_count, "iterator/backward: visited") + + // Test removal (and on_remove callback) + rand.shuffle(inserted_keys[:]) + callback_count := entry_count + tree.user_data = &callback_count + tree.on_remove = proc(key: Key, value: Value, user_data: rawptr) { + (^int)(user_data)^ -= 1 + } + for k, i in inserted_keys { + node := rb.find(&tree, k) + testing.expect(t, node != nil, "remove: find (pre)") + + ok := rb.remove(&tree, k) + testing.expect(t, ok, "remove: succeeds") + testing.expect(t, entry_count - (i + 1) == rb.len(&tree), "remove: len (post)") + validate_rbtree(t, &tree) + + testing.expect(t, nil == rb.find(&tree, k), "remove: find (post") + } + testing.expect(t, rb.len(&tree) == 0, "remove: len should be 0") + testing.expectf(t, callback_count == 0, "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count) + testing.expect(t, rb.first(&tree) == nil, "remove: first should be nil") + testing.expect(t, rb.last(&tree) == nil, "remove: last should be nil") + + // Refill the tree. + for k in inserted_keys { + rb.find_or_insert(&tree, k, 42) + } + + // Test that removing the node doesn't break the iterator. + callback_count = entry_count + iter = rb.iterator(&tree, rb.Direction.Forward) + if node := rb.iterator_get(&iter); node != nil { + k := node.key + + ok := rb.iterator_remove(&iter) + testing.expect(t, ok, "iterator/remove: success") + + ok = rb.iterator_remove(&iter) + testing.expect(t, !ok, "iterator/remove: redundant removes should fail") + + testing.expect(t, rb.find(&tree, k) == nil, "iterator/remove: node should be gone") + testing.expect(t, rb.iterator_get(&iter) == nil, "iterator/remove: get should return nil") + + // Ensure that iterator_next still works. + node, ok = rb.iterator_next(&iter) + testing.expect(t, ok == (rb.len(&tree) > 0), "iterator/remove: next should return false") + testing.expect(t, node == rb.first(&tree), "iterator/remove: next should return first") + + validate_rbtree(t, &tree) + } + testing.expect(t, rb.len(&tree) == entry_count - 1, "iterator/remove: len should drop by 1") + + rb.destroy(&tree) + testing.expect(t, rb.len(&tree) == 0, "destroy: len should be 0") + testing.expectf(t, callback_count == 0, "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count) + + // print_tree_node(tree._root) + delete(inserted_map) + delete(inserted_keys) + testing.expectf(t, len(track.allocation_map) == 0, "Expected 0 leaks, have %v", len(track.allocation_map)) + testing.expectf(t, len(track.bad_free_array) == 0, "Expected 0 bad frees, have %v", len(track.bad_free_array)) + return +} + +@(test) +test_rbtree :: proc(t: ^testing.T) { + test_rbtree_integer(t, u16, u16) +} + +print_tree_node :: proc(n: ^$N/rb.Node($Key, $Value), indent := 0) { + if n == nil { + fmt.println("") + return + } + if n.right != nil { + print_tree_node(n.right, indent + 1) + } + for _ in 0..", n.key) + } + if n.left != nil { + print_tree_node(n.left, indent + 1) + } +} + +validate_rbtree :: proc(t: ^testing.T, tree: ^$T/rb.Tree($Key, $Value)) { + verify_rbtree_propery_1(t, tree._root) + verify_rbtree_propery_2(t, tree._root) + /* Property 3 is implicit */ + verify_rbtree_propery_4(t, tree._root) + verify_rbtree_propery_5(t, tree._root) +} + +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.") + if n == nil { + return + } + verify_rbtree_propery_1(t, n._left) + verify_rbtree_propery_1(t, n._right) +} + +verify_rbtree_propery_2 :: proc(t: ^testing.T, root: ^$N/rb.Node($Key, $Value)) { + testing.expect(t, rb.node_color(root) == .Black, "Property #2: Root node should be black.") +} + +verify_rbtree_propery_4 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) { + if rb.node_color(n) == .Red { + // A red node's left, right and parent should be black + all_black := rb.node_color(n._left) == .Black && rb.node_color(n._right) == .Black && rb.node_color(n._parent) == .Black + testing.expect(t, all_black, "Property #3: Red node's children + parent must be black.") + } + if n == nil { + return + } + verify_rbtree_propery_4(t, n._left) + verify_rbtree_propery_4(t, n._right) +} + +verify_rbtree_propery_5 :: proc(t: ^testing.T, root: ^$N/rb.Node($Key, $Value)) { + black_count_path := -1 + verify_rbtree_propery_5_helper(t, root, 0, &black_count_path) +} +verify_rbtree_propery_5_helper :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value), black_count: int, path_black_count: ^int) { + black_count := black_count + + if rb.node_color(n) == .Black { + black_count += 1 + } + if n == nil { + if path_black_count^ == -1 { + path_black_count^ = black_count + } else { + testing.expect(t, black_count == path_black_count^, "Property #5: Paths from a node to its leaves contain same black count.") + } + return + } + verify_rbtree_propery_5_helper(t, n._left, black_count, path_black_count) + verify_rbtree_propery_5_helper(t, n._right, black_count, path_black_count) +} +// Properties 4 and 5 together guarantee that no path in the tree is more than about twice as long as any other path, +// which guarantees that it has O(log n) height. diff --git a/tests/core/container/test_core_small_array.odin b/tests/core/container/test_core_small_array.odin index 78998de16..580df793e 100644 --- a/tests/core/container/test_core_small_array.odin +++ b/tests/core/container/test_core_small_array.odin @@ -3,44 +3,47 @@ package test_core_container import "core:testing" import "core:container/small_array" -import tc "tests:common" - -@(test) -test_small_array :: proc(t: ^testing.T) { - tc.log(t, "Testing small_array") - - test_small_array_removes(t) - test_small_array_inject_at(t) -} - @(test) test_small_array_removes :: proc(t: ^testing.T) { - array: small_array.Small_Array(10, int) - small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + array: small_array.Small_Array(10, int) + small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - small_array.ordered_remove(&array, 0) - expect_equal(t, small_array.slice(&array), []int { 1, 2, 3, 4, 5, 6, 7, 8, 9 }) - small_array.ordered_remove(&array, 5) - expect_equal(t, small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 8, 9 }) - small_array.ordered_remove(&array, 6) - expect_equal(t, small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 9 }) - small_array.unordered_remove(&array, 0) - expect_equal(t, small_array.slice(&array), []int { 9, 2, 3, 4, 5, 7 }) - small_array.unordered_remove(&array, 2) - expect_equal(t, small_array.slice(&array), []int { 9, 2, 7, 4, 5 }) - small_array.unordered_remove(&array, 4) - expect_equal(t, small_array.slice(&array), []int { 9, 2, 7, 4 }) + small_array.ordered_remove(&array, 0) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 6, 7, 8, 9 })) + small_array.ordered_remove(&array, 5) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 8, 9 })) + small_array.ordered_remove(&array, 6) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 9 })) + small_array.unordered_remove(&array, 0) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 3, 4, 5, 7 })) + small_array.unordered_remove(&array, 2) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 7, 4, 5 })) + small_array.unordered_remove(&array, 4) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 7, 4 })) } @(test) test_small_array_inject_at :: proc(t: ^testing.T) { - array: small_array.Small_Array(13, int) - small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + array: small_array.Small_Array(13, int) + small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - tc.expect(t, small_array.inject_at(&array, 0, 0), "Expected to be able to inject into small array") - expect_equal(t, small_array.slice(&array), []int { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }) - tc.expect(t, small_array.inject_at(&array, 0, 5), "Expected to be able to inject into small array") - expect_equal(t, small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9 }) - tc.expect(t, small_array.inject_at(&array, 0, small_array.len(array)), "Expected to be able to inject into small array") - expect_equal(t, small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 0 }) + testing.expect(t, small_array.inject_at(&array, 0, 0), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })) + testing.expect(t, small_array.inject_at(&array, 0, 5), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9 })) + testing.expect(t, small_array.inject_at(&array, 0, small_array.len(array)), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 0 })) +} + +slice_equal :: proc(a, b: []int) -> bool { + if len(a) != len(b) { + return false + } + + for a, i in a { + if b[i] != a { + return false + } + } + return true } diff --git a/tests/core/crypto/test_core_crypto.odin b/tests/core/crypto/test_core_crypto.odin index 72d8e7c78..f3f76646b 100644 --- a/tests/core/crypto/test_core_crypto.odin +++ b/tests/core/crypto/test_core_crypto.odin @@ -13,40 +13,20 @@ package test_core_crypto */ import "core:encoding/hex" -import "core:fmt" import "core:mem" import "core:testing" +import "base:runtime" +import "core:log" import "core:crypto" import "core:crypto/chacha20" import "core:crypto/chacha20poly1305" -import tc "tests:common" - -main :: proc() { - t := testing.T{} - - test_rand_bytes(&t) - - test_hash(&t) - test_mac(&t) - test_kdf(&t) // After hash/mac tests because those should pass first. - test_ecc25519(&t) - - test_chacha20(&t) - test_chacha20poly1305(&t) - test_sha3_variants(&t) - - bench_crypto(&t) - - tc.report(&t) -} - _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) { - tc.log(t, "Testing (X)ChaCha20") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() // Test cases taken from RFC 8439, and draft-irtf-cfrg-xchacha-03 plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR) @@ -89,14 +69,12 @@ test_chacha20 :: proc(t: ^testing.T) { chacha20.xor_bytes(&ctx, derived_ciphertext[:], plaintext[:]) derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_ciphertext_str == ciphertext_str, - fmt.tprintf( - "Expected %s for xor_bytes(plaintext_str), but got %s instead", - ciphertext_str, - derived_ciphertext_str, - ), + "Expected %s for xor_bytes(plaintext_str), but got %s instead", + ciphertext_str, + derived_ciphertext_str, ) xkey := [chacha20.KEY_SIZE]byte { @@ -136,21 +114,17 @@ test_chacha20 :: proc(t: ^testing.T) { chacha20.xor_bytes(&ctx, derived_ciphertext[:], plaintext[:]) derived_ciphertext_str = string(hex.encode(derived_ciphertext[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_ciphertext_str == xciphertext_str, - fmt.tprintf( - "Expected %s for xor_bytes(plaintext_str), but got %s instead", - xciphertext_str, - derived_ciphertext_str, - ), + "Expected %s for xor_bytes(plaintext_str), but got %s instead", + xciphertext_str, + derived_ciphertext_str, ) } @(test) test_chacha20poly1305 :: proc(t: ^testing.T) { - tc.log(t, "Testing chacha20poly1205") - plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR) aad := [12]byte { @@ -208,25 +182,21 @@ test_chacha20poly1305 :: proc(t: ^testing.T) { ) derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_ciphertext_str == ciphertext_str, - fmt.tprintf( - "Expected ciphertext %s for encrypt(aad, plaintext), but got %s instead", - ciphertext_str, - derived_ciphertext_str, - ), + "Expected ciphertext %s for encrypt(aad, plaintext), but got %s instead", + ciphertext_str, + derived_ciphertext_str, ) derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_tag_str == tag_str, - fmt.tprintf( - "Expected tag %s for encrypt(aad, plaintext), but got %s instead", - tag_str, - derived_tag_str, - ), + "Expected tag %s for encrypt(aad, plaintext), but got %s instead", + tag_str, + derived_tag_str, ) derived_plaintext: [114]byte @@ -239,15 +209,13 @@ test_chacha20poly1305 :: proc(t: ^testing.T) { ciphertext[:], ) derived_plaintext_str := string(derived_plaintext[:]) - tc.expect(t, ok, "Expected true for decrypt(tag, aad, ciphertext)") - tc.expect( + testing.expect(t, ok, "Expected true for decrypt(tag, aad, ciphertext)") + testing.expectf( t, derived_plaintext_str == _PLAINTEXT_SUNSCREEN_STR, - fmt.tprintf( - "Expected plaintext %s for decrypt(tag, aad, ciphertext), but got %s instead", - _PLAINTEXT_SUNSCREEN_STR, - derived_plaintext_str, - ), + "Expected plaintext %s for decrypt(tag, aad, ciphertext), but got %s instead", + _PLAINTEXT_SUNSCREEN_STR, + derived_plaintext_str, ) derived_ciphertext[0] ~= 0xa5 @@ -259,7 +227,7 @@ test_chacha20poly1305 :: proc(t: ^testing.T) { aad[:], derived_ciphertext[:], ) - tc.expect(t, !ok, "Expected false for decrypt(tag, aad, corrupted_ciphertext)") + testing.expect(t, !ok, "Expected false for decrypt(tag, aad, corrupted_ciphertext)") aad[0] ~= 0xa5 ok = chacha20poly1305.decrypt( @@ -270,15 +238,13 @@ test_chacha20poly1305 :: proc(t: ^testing.T) { aad[:], ciphertext[:], ) - tc.expect(t, !ok, "Expected false for decrypt(tag, corrupted_aad, ciphertext)") + testing.expect(t, !ok, "Expected false for decrypt(tag, corrupted_aad, ciphertext)") } @(test) test_rand_bytes :: proc(t: ^testing.T) { - tc.log(t, "Testing rand_bytes") - - if !crypto.has_rand_bytes() { - tc.log(t, "rand_bytes not supported - skipping") + if !crypto.HAS_RAND_BYTES { + log.info("rand_bytes not supported - skipping") return } @@ -306,10 +272,5 @@ test_rand_bytes :: proc(t: ^testing.T) { break } } - - tc.expect( - t, - seems_ok, - "Expected to randomize the head and tail of the buffer within a handful of attempts", - ) + testing.expect(t, seems_ok, "Expected to randomize the head and tail of the buffer within a handful of attempts") } diff --git a/tests/core/crypto/test_core_crypto_aes.odin b/tests/core/crypto/test_core_crypto_aes.odin new file mode 100644 index 000000000..c2fa2835c --- /dev/null +++ b/tests/core/crypto/test_core_crypto_aes.odin @@ -0,0 +1,444 @@ +package test_core_crypto + +import "base:runtime" +import "core:encoding/hex" +import "core:log" +import "core:testing" + +import "core:crypto/aes" +import "core:crypto/sha2" + +@(test) +test_aes :: proc(t: ^testing.T) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + impls := make([dynamic]aes.Implementation, 0, 2) + defer delete(impls) + 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) + } +} + +test_aes_ecb :: proc(t: ^testing.T, impl: aes.Implementation) { + log.debugf("Testing AES-ECB/%v", impl) + + test_vectors := []struct { + key: string, + plaintext: string, + ciphertext: string, + } { + // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + { + "2b7e151628aed2a6abf7158809cf4f3c", + "6bc1bee22e409f96e93d7e117393172a", + "3ad77bb40d7a3660a89ecaf32466ef97", + }, + { + "2b7e151628aed2a6abf7158809cf4f3c", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "f5d3d58503b9699de785895a96fdbaaf", + }, + { + "2b7e151628aed2a6abf7158809cf4f3c", + "30c81c46a35ce411e5fbc1191a0a52ef", + "43b1cd7f598ece23881b00e3ed030688", + }, + { + "2b7e151628aed2a6abf7158809cf4f3c", + "f69f2445df4f9b17ad2b417be66c3710", + "7b0c785e27e8ad3f8223207104725dd4", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "6bc1bee22e409f96e93d7e117393172a", + "bd334f1d6e45f25ff712a214571fa5cc", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "974104846d0ad3ad7734ecb3ecee4eef", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "30c81c46a35ce411e5fbc1191a0a52ef", + "ef7afd2270e2e60adce0ba2face6444e", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "f69f2445df4f9b17ad2b417be66c3710", + "9a4b41ba738d6c72fb16691603c18e0e", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "6bc1bee22e409f96e93d7e117393172a", + "f3eed1bdb5d2a03c064b5a7e3db181f8", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "591ccb10d410ed26dc5ba74a31362870", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "30c81c46a35ce411e5fbc1191a0a52ef", + "b6ed21b99ca6f4f9f153e7b1beafed1d", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "f69f2445df4f9b17ad2b417be66c3710", + "23304b7a39f9f3ff067d8d8f9e24ecc7", + }, + } + for v, _ in test_vectors { + key, _ := hex.decode(transmute([]byte)(v.key), context.temp_allocator) + plaintext, _ := hex.decode(transmute([]byte)(v.plaintext), context.temp_allocator) + ciphertext, _ := hex.decode(transmute([]byte)(v.ciphertext), context.temp_allocator) + + ctx: aes.Context_ECB + dst: [aes.BLOCK_SIZE]byte + aes.init_ecb(&ctx, key, impl) + + aes.encrypt_ecb(&ctx, dst[:], plaintext) + dst_str := string(hex.encode(dst[:], context.temp_allocator)) + testing.expectf( + t, + dst_str == v.ciphertext, + "AES-ECB/%v: Expected: %s for encrypt(%s, %s), but got %s instead", + impl, + v.ciphertext, + v.key, + v.plaintext, + dst_str, + ) + + aes.decrypt_ecb(&ctx, dst[:], ciphertext) + dst_str = string(hex.encode(dst[:], context.temp_allocator)) + testing.expectf( + t, + dst_str == v.plaintext, + "AES-ECB/%v: Expected: %s for decrypt(%s, %s), but got %s instead", + impl, + v.plaintext, + v.key, + v.ciphertext, + dst_str, + ) + } +} + +test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { + log.debugf("Testing AES-CTR/%v", impl) + + test_vectors := []struct { + key: string, + iv: string, + plaintext: string, + ciphertext: string, + } { + // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + { + "2b7e151628aed2a6abf7158809cf4f3c", + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "1abc932417521ca24f2b0459fe7e6e0b090339ec0aa6faefd5ccc2c6f4ce8e941e36b26bd1ebc670d1bd1d665620abf74f78a7f6d29809585a97daec58c6b050", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6", + }, + } + 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) + plaintext, _ := hex.decode(transmute([]byte)(v.plaintext), context.temp_allocator) + ciphertext, _ := hex.decode(transmute([]byte)(v.ciphertext), context.temp_allocator) + + dst := make([]byte, len(ciphertext), context.temp_allocator) + + ctx: aes.Context_CTR + aes.init_ctr(&ctx, key, iv, impl) + + aes.xor_bytes_ctr(&ctx, dst, plaintext) + + dst_str := string(hex.encode(dst[:], context.temp_allocator)) + testing.expectf( + t, + dst_str == v.ciphertext, + "AES-CTR/%v: Expected: %s for encrypt(%s, %s, %s), but got %s instead", + impl, + v.ciphertext, + v.key, + v.iv, + v.plaintext, + dst_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. + + tmp := make([]byte, 2048, context.temp_allocator) + + ctx: aes.Context_CTR + key: [aes.KEY_SIZE_256]byte + nonce: [aes.CTR_IV_SIZE]byte + aes.init_ctr(&ctx, key[:], nonce[:], impl) + + h_ctx: sha2.Context_512 + sha2.init_512_256(&h_ctx) + + for i := 1; i < 2048; i = i + 1 { + aes.keystream_bytes_ctr(&ctx, tmp[:i]) + sha2.update(&h_ctx, tmp[:i]) + } + + digest: [32]byte + sha2.final(&h_ctx, digest[:]) + digest_str := string(hex.encode(digest[:], context.temp_allocator)) + + expected_digest_str := "d4445343afeb9d1237f95b10d00358aed4c1d7d57c9fe480cd0afb5e2ffd448c" + testing.expectf( + t, + expected_digest_str == digest_str, + "AES-CTR/%v: Expected %s for keystream digest, but got %s instead", + impl, + expected_digest_str, + 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/crypto/test_core_crypto_ecc25519.odin b/tests/core/crypto/test_core_crypto_ecc25519.odin index 5ea008f90..fec4fa38e 100644 --- a/tests/core/crypto/test_core_crypto_ecc25519.odin +++ b/tests/core/crypto/test_core_crypto_ecc25519.odin @@ -1,8 +1,6 @@ package test_core_crypto -import "base:runtime" import "core:encoding/hex" -import "core:fmt" import "core:testing" import field "core:crypto/_fiat/field_curve25519" @@ -10,25 +8,8 @@ import "core:crypto/ed25519" import "core:crypto/ristretto255" import "core:crypto/x25519" -import tc "tests:common" - -@(test) -test_ecc25519 :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - tc.log(t, "Testing curve25519 ECC") - - test_sqrt_ratio_m1(t) - test_ristretto255(t) - - test_ed25519(t) - test_x25519(t) -} - @(test) test_sqrt_ratio_m1 :: proc(t: ^testing.T) { - tc.log(t, "Testing sqrt_ratio_m1") - test_vectors := []struct { u: string, v: string, @@ -77,9 +58,9 @@ test_sqrt_ratio_m1 :: proc(t: ^testing.T) { v_bytes, _ := hex.decode(transmute([]byte)(v.v), context.temp_allocator) r_bytes, _ := hex.decode(transmute([]byte)(v.r), context.temp_allocator) - u_ := transmute(^[32]byte)(raw_data(u_bytes)) - v_ := transmute(^[32]byte)(raw_data(v_bytes)) - r_ := transmute(^[32]byte)(raw_data(r_bytes)) + u_ := (^[32]byte)(raw_data(u_bytes)) + v_ := (^[32]byte)(raw_data(v_bytes)) + r_ := (^[32]byte)(raw_data(r_bytes)) u, vee, r: field.Tight_Field_Element field.fe_from_bytes(&u, u_) @@ -90,25 +71,21 @@ test_sqrt_ratio_m1 :: proc(t: ^testing.T) { field.fe_relax_cast(&vee), ) - tc.expect( + testing.expectf( t, (was_square == 1) == v.was_square && field.fe_equal_bytes(&r, r_) == 1, - fmt.tprintf( - "Expected (%v, %s) for SQRT_RATIO_M1(%s, %s), got %s", - v.was_square, - v.r, - v.u, - v.v, - fe_str(&r), - ), + "Expected (%v, %s) for SQRT_RATIO_M1(%s, %s), got %s", + v.was_square, + v.r, + v.u, + v.v, + fe_str(&r), ) } } @(test) test_ristretto255 :: proc(t: ^testing.T) { - tc.log(t, "Testing ristretto255") - ge_gen: ristretto255.Group_Element ristretto255.ge_generator(&ge_gen) @@ -158,7 +135,7 @@ test_ristretto255 :: proc(t: ^testing.T) { ge: ristretto255.Group_Element ok := ristretto255.ge_set_bytes(&ge, b) - tc.expect(t, !ok, fmt.tprintf("Expected false for %s", x)) + testing.expectf(t, !ok, "Expected false for %s", x) } generator_multiples := []string { @@ -185,22 +162,20 @@ test_ristretto255 :: proc(t: ^testing.T) { ge := &ges[i] ok := ristretto255.ge_set_bytes(ge, b) - tc.expect(t, ok, fmt.tprintf("Expected true for %s", x)) + testing.expectf(t, ok, "Expected true for %s", x) x_check := ge_str(ge) - tc.expect( + testing.expectf( t, x == x_check, - fmt.tprintf( - "Expected %s (round-trip) but got %s instead", - x, - x_check, - ), + "Expected %s (round-trip) but got %s instead", + x, + x_check, ) if i == 1 { - tc.expect( + testing.expect( t, ristretto255.ge_equal(ge, &ge_gen) == 1, "Expected element 1 to be the generator", @@ -217,41 +192,35 @@ test_ristretto255 :: proc(t: ^testing.T) { ristretto255.ge_scalarmult_generator(&ge_check, &sc) x_check := ge_str(&ge_check) - tc.expect( + testing.expectf( t, x_check == generator_multiples[i], - fmt.tprintf( - "Expected %s for G * %d (specialized), got %s", - generator_multiples[i], - i, - x_check, - ), + "Expected %s for G * %d (specialized), got %s", + generator_multiples[i], + i, + x_check, ) ristretto255.ge_scalarmult(&ge_check, &ges[1], &sc) x_check = ge_str(&ge_check) - tc.expect( + testing.expectf( t, x_check == generator_multiples[i], - fmt.tprintf( - "Expected %s for G * %d (generic), got %s (slow compare)", - generator_multiples[i], - i, - x_check, - ), + "Expected %s for G * %d (generic), got %s (slow compare)", + generator_multiples[i], + i, + x_check, ) ristretto255.ge_scalarmult_vartime(&ge_check, &ges[1], &sc) x_check = ge_str(&ge_check) - tc.expect( + testing.expectf( t, x_check == generator_multiples[i], - fmt.tprintf( - "Expected %s for G * %d (generic vartime), got %s (slow compare)", - generator_multiples[i], - i, - x_check, - ), + "Expected %s for G * %d (generic vartime), got %s (slow compare)", + generator_multiples[i], + i, + x_check, ) switch i { @@ -261,28 +230,24 @@ test_ristretto255 :: proc(t: ^testing.T) { ristretto255.ge_add(&ge_check, ge_prev, &ge_gen) x_check = ge_str(&ge_check) - tc.expect( + testing.expectf( t, x_check == generator_multiples[i], - fmt.tprintf( - "Expected %s for ges[%d] + ges[%d], got %s (slow compare)", - generator_multiples[i], - i-1, - 1, - x_check, - ), + "Expected %s for ges[%d] + ges[%d], got %s (slow compare)", + generator_multiples[i], + i-1, + 1, + x_check, ) - tc.expect( + testing.expectf( t, ristretto255.ge_equal(&ges[i], &ge_check) == 1, - fmt.tprintf( - "Expected %s for ges[%d] + ges[%d], got %s (fast compare)", - generator_multiples[i], - i-1, - 1, - x_check, - ), + "Expected %s for ges[%d] + ges[%d], got %s (fast compare)", + generator_multiples[i], + i-1, + 1, + x_check, ) } } @@ -344,22 +309,18 @@ test_ristretto255 :: proc(t: ^testing.T) { ristretto255.ge_set_wide_bytes(&ge, in_bytes) ge_check := ge_str(&ge) - tc.expect( + testing.expectf( t, ge_check == v.output, - fmt.tprintf( - "Expected %s for %s, got %s", - v.output, - ge_check, - ), + "Expected %s for %s, got %s", + v.output, + ge_check, ) } } @(test) test_ed25519 :: proc(t: ^testing.T) { - tc.log(t, "Testing ed25519") - test_vectors_rfc := []struct { priv_key: string, pub_key: string, @@ -401,87 +362,73 @@ test_ed25519 :: proc(t: ^testing.T) { priv_key: ed25519.Private_Key ok := ed25519.private_key_set_bytes(&priv_key, priv_bytes) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected %s to be a valid private key", - v.priv_key, - ), + "Expected %s to be a valid private key", + v.priv_key, ) key_bytes: [32]byte ed25519.private_key_bytes(&priv_key, key_bytes[:]) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected private key %s round-trip, got %s", - v.priv_key, - string(hex.encode(key_bytes[:], context.temp_allocator)), - ), + "Expected private key %s round-trip, got %s", + v.priv_key, + string(hex.encode(key_bytes[:], context.temp_allocator)), ) pub_key: ed25519.Public_Key ok = ed25519.public_key_set_bytes(&pub_key, pub_bytes) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected %s to be a valid public key (priv->pub: %s)", - v.pub_key, - string(hex.encode(priv_key._pub_key._b[:], context.temp_allocator)), - ), + "Expected %s to be a valid public key (priv->pub: %s)", + v.pub_key, + string(hex.encode(priv_key._pub_key._b[:], context.temp_allocator)), ) ed25519.public_key_bytes(&pub_key, key_bytes[:]) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected public key %s round-trip, got %s", - v.pub_key, - string(hex.encode(key_bytes[:], context.temp_allocator)), - ), + "Expected public key %s round-trip, got %s", + v.pub_key, + string(hex.encode(key_bytes[:], context.temp_allocator)), ) sig: [ed25519.SIGNATURE_SIZE]byte ed25519.sign(&priv_key, msg_bytes, sig[:]) x := string(hex.encode(sig[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, x == v.sig, - fmt.tprintf( - "Expected %s for sign(%s, %s), got %s", - v.sig, - v.priv_key, - v.msg, - x, - ), + "Expected %s for sign(%s, %s), got %s", + v.sig, + v.priv_key, + v.msg, + x, ) ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected true for verify(%s, %s, %s)", - v.pub_key, - v.msg, - v.sig, - ), + "Expected true for verify(%s, %s, %s)", + v.pub_key, + v.msg, + v.sig, ) ok = ed25519.verify(&priv_key._pub_key, msg_bytes, sig_bytes) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected true for verify(pub(%s), %s %s)", - v.priv_key, - v.msg, - v.sig, - ), + "Expected true for verify(pub(%s), %s %s)", + v.priv_key, + v.msg, + v.sig, ) // Corrupt the message and make sure verification fails. @@ -493,15 +440,13 @@ test_ed25519 :: proc(t: ^testing.T) { msg_bytes[0] = msg_bytes[0] ~ 69 } ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes) - tc.expect( + testing.expectf( t, ok == false, - fmt.tprintf( - "Expected false for verify(%s, %s (corrupted), %s)", - v.pub_key, - v.msg, - v.sig, - ), + "Expected false for verify(%s, %s (corrupted), %s)", + v.pub_key, + v.msg, + v.sig, ) } @@ -634,15 +579,13 @@ test_ed25519 :: proc(t: ^testing.T) { pub_key: ed25519.Public_Key ok := ed25519.public_key_set_bytes(&pub_key, pub_bytes) - tc.expect( + testing.expectf( t, ok == v.pub_key_ok, - fmt.tprintf( - "speccheck/%d: Expected %s to be a (in)valid public key, got %v", - i, - v.pub_key, - ok, - ), + "speccheck/%d: Expected %s to be a (in)valid public key, got %v", + i, + v.pub_key, + ok, ) // If A is rejected for being non-canonical, skip signature check. @@ -651,17 +594,15 @@ test_ed25519 :: proc(t: ^testing.T) { } ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes) - tc.expect( + testing.expectf( t, ok == v.sig_ok, - fmt.tprintf( - "speccheck/%d Expected %v for verify(%s, %s, %s)", - i, - v.sig_ok, - v.pub_key, - v.msg, - v.sig, - ), + "speccheck/%d Expected %v for verify(%s, %s, %s)", + i, + v.sig_ok, + v.pub_key, + v.msg, + v.sig, ) // If the signature is accepted, skip the relaxed signature check. @@ -670,25 +611,21 @@ test_ed25519 :: proc(t: ^testing.T) { } ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes, true) - tc.expect( + testing.expectf( t, ok == v.sig_ok_relaxed, - fmt.tprintf( - "speccheck/%d Expected %v for verify(%s, %s, %s, true)", - i, - v.sig_ok_relaxed, - v.pub_key, - v.msg, - v.sig, - ), + "speccheck/%d Expected %v for verify(%s, %s, %s, true)", + i, + v.sig_ok_relaxed, + v.pub_key, + v.msg, + v.sig, ) } } @(test) test_x25519 :: proc(t: ^testing.T) { - tc.log(t, "Testing X25519") - // Local copy of this so that the base point doesn't need to be exported. _BASE_POINT: [32]byte = { 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -720,17 +657,15 @@ test_x25519 :: proc(t: ^testing.T) { x25519.scalarmult(derived_point[:], scalar[:], point[:]) derived_point_str := string(hex.encode(derived_point[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_point_str == v.product, - fmt.tprintf( - "Expected %s for %s * %s, but got %s instead", - v.product, - v.scalar, - v.point, - derived_point_str, - ), - ) + "Expected %s for %s * %s, but got %s instead", + v.product, + v.scalar, + v.point, + derived_point_str, + ) // Abuse the test vectors to sanity-check the scalar-basepoint multiply. p1, p2: [x25519.POINT_SIZE]byte @@ -738,15 +673,13 @@ test_x25519 :: proc(t: ^testing.T) { x25519.scalarmult(p2[:], scalar[:], _BASE_POINT[:]) p1_str := string(hex.encode(p1[:], context.temp_allocator)) p2_str := string(hex.encode(p2[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, p1_str == p2_str, - fmt.tprintf( - "Expected %s for %s * basepoint, but got %s instead", - p2_str, - v.scalar, - p1_str, - ), + "Expected %s for %s * basepoint, but got %s instead", + p2_str, + v.scalar, + p1_str, ) } } @@ -763,4 +696,4 @@ fe_str :: proc(fe: ^field.Tight_Field_Element) -> string { b: [32]byte field.fe_to_bytes(&b, fe) return string(hex.encode(b[:], context.temp_allocator)) -} +} \ No newline at end of file diff --git a/tests/core/crypto/test_core_crypto_hash.odin b/tests/core/crypto/test_core_crypto_hash.odin index c4e8e8dd7..9a9d0cc76 100644 --- a/tests/core/crypto/test_core_crypto_hash.odin +++ b/tests/core/crypto/test_core_crypto_hash.odin @@ -3,23 +3,17 @@ package test_core_crypto import "base:runtime" import "core:bytes" import "core:encoding/hex" -import "core:fmt" import "core:strings" import "core:testing" - import "core:crypto/hash" -import tc "tests:common" - @(test) test_hash :: proc(t: ^testing.T) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - tc.log(t, "Testing Hashes") - // TODO: // - Stick the test vectors in a JSON file or something. - data_1_000_000_a := strings.repeat("a", 1_000_000) + data_1_000_000_a := strings.repeat("a", 1_000_000, context.temp_allocator) digest: [hash.MAX_DIGEST_SIZE]byte test_vectors := []struct{ @@ -496,16 +490,14 @@ test_hash :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.hash, - fmt.tprintf( - "%s/incremental: Expected: %s for input of %s, but got %s instead", - algo_name, - v.hash, - v.str, - dst_str, - ), + "%s/incremental: Expected: %s for input of %s, but got %s instead", + algo_name, + v.hash, + v.str, + dst_str, ) } @@ -521,25 +513,21 @@ test_hash :: proc(t: ^testing.T) { // still correct. digest_sz := hash.DIGEST_SIZES[algo] block_sz := hash.BLOCK_SIZES[algo] - tc.expect( + testing.expectf( t, digest_sz <= hash.MAX_DIGEST_SIZE, - fmt.tprintf( - "%s: Digest size %d exceeds max %d", - algo_name, - digest_sz, - hash.MAX_DIGEST_SIZE, - ), + "%s: Digest size %d exceeds max %d", + algo_name, + digest_sz, + hash.MAX_DIGEST_SIZE, ) - tc.expect( + testing.expectf( t, block_sz <= hash.MAX_BLOCK_SIZE, - fmt.tprintf( - "%s: Block size %d exceeds max %d", - algo_name, - block_sz, - hash.MAX_BLOCK_SIZE, - ), + "%s: Block size %d exceeds max %d", + algo_name, + block_sz, + hash.MAX_BLOCK_SIZE, ) // Exercise most of the happy-path for the high level interface. @@ -553,15 +541,13 @@ test_hash :: proc(t: ^testing.T) { a_str := string(hex.encode(digest_a, context.temp_allocator)) b_str := string(hex.encode(digest_b, context.temp_allocator)) - tc.expect( + testing.expectf( t, a_str == b_str, - fmt.tprintf( - "%s/cmp: Expected: %s (hash_stream) == %s (hash_bytes)", - algo_name, - a_str, - b_str, - ), + "%s/cmp: Expected: %s (hash_stream) == %s (hash_bytes)", + algo_name, + a_str, + b_str, ) // Exercise the rolling digest functionality, which also covers @@ -571,25 +557,21 @@ test_hash :: proc(t: ^testing.T) { api_algo := hash.algorithm(&ctx) api_digest_size := hash.digest_size(&ctx) - tc.expect( + testing.expectf( t, algo == api_algo, - fmt.tprintf( - "%s/algorithm: Expected: %v but got %v instead", - algo_name, - algo, - api_algo, - ), + "%s/algorithm: Expected: %v but got %v instead", + algo_name, + algo, + api_algo, ) - tc.expect( + testing.expectf( t, hash.DIGEST_SIZES[algo] == api_digest_size, - fmt.tprintf( - "%s/digest_size: Expected: %d but got %d instead", - algo_name, - hash.DIGEST_SIZES[algo], - api_digest_size, - ), + "%s/digest_size: Expected: %d but got %d instead", + algo_name, + hash.DIGEST_SIZES[algo], + api_digest_size, ) hash.update(&ctx, digest_a) @@ -604,16 +586,14 @@ test_hash :: proc(t: ^testing.T) { b_str = string(hex.encode(digest_b, context.temp_allocator)) c_str := string(hex.encode(digest_c, context.temp_allocator)) - tc.expect( + testing.expectf( t, a_str == b_str && b_str == c_str, - fmt.tprintf( - "%s/rolling: Expected: %s (first) == %s (second) == %s (third)", - algo_name, - a_str, - b_str, - c_str, - ), + "%s/rolling: Expected: %s (first) == %s (second) == %s (third)", + algo_name, + a_str, + b_str, + c_str, ) } -} +} \ No newline at end of file diff --git a/tests/core/crypto/test_core_crypto_kdf.odin b/tests/core/crypto/test_core_crypto_kdf.odin index 73177d8be..c15dc2206 100644 --- a/tests/core/crypto/test_core_crypto_kdf.odin +++ b/tests/core/crypto/test_core_crypto_kdf.odin @@ -2,28 +2,14 @@ package test_core_crypto import "base:runtime" import "core:encoding/hex" -import "core:fmt" import "core:testing" - import "core:crypto/hash" import "core:crypto/hkdf" import "core:crypto/pbkdf2" -import tc "tests:common" - -@(test) -test_kdf :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - tc.log(t, "Testing KDFs") - - test_hkdf(t) - test_pbkdf2(t) -} - @(test) test_hkdf :: proc(t: ^testing.T) { - tc.log(t, "Testing HKDF") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() tmp: [128]byte // Good enough. @@ -70,25 +56,23 @@ test_hkdf :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.okm, - fmt.tprintf( - "HKDF-%s: Expected: %s for input of (%s, %s, %s), but got %s instead", - algo_name, - v.okm, - v.ikm, - v.salt, - v.info, - dst_str, - ), + "HKDF-%s: Expected: %s for input of (%s, %s, %s), but got %s instead", + algo_name, + v.okm, + v.ikm, + v.salt, + v.info, + dst_str, ) } } @(test) test_pbkdf2 :: proc(t: ^testing.T) { - tc.log(t, "Testing PBKDF2") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() tmp: [64]byte // 512-bits is enough for every output for now. @@ -174,18 +158,16 @@ test_pbkdf2 :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.dk, - fmt.tprintf( - "HMAC-%s: Expected: %s for input of (%s, %s, %d), but got %s instead", - algo_name, - v.dk, - v.password, - v.salt, - v.iterations, - dst_str, - ), + "PBKDF2-%s: Expected: %s for input of (%s, %s, %d), but got %s instead", + algo_name, + v.dk, + v.password, + v.salt, + v.iterations, + dst_str, ) } } diff --git a/tests/core/crypto/test_core_crypto_mac.odin b/tests/core/crypto/test_core_crypto_mac.odin index f2eeacb19..ed95ba0ad 100644 --- a/tests/core/crypto/test_core_crypto_mac.odin +++ b/tests/core/crypto/test_core_crypto_mac.odin @@ -2,30 +2,17 @@ package test_core_crypto import "base:runtime" import "core:encoding/hex" -import "core:fmt" import "core:mem" import "core:testing" - import "core:crypto/hash" import "core:crypto/hmac" import "core:crypto/poly1305" import "core:crypto/siphash" -import tc "tests:common" - -@(test) -test_mac :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - tc.log(t, "Testing MACs") - - test_hmac(t) - test_poly1305(t) - test_siphash_2_4(t) -} - @(test) test_hmac :: proc(t: ^testing.T) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + // Test cases pulled out of RFC 6234, note that HMAC is a generic // construct so as long as the underlying hash is correct and all // the code paths are covered the implementation is "fine", so @@ -86,40 +73,36 @@ test_hmac :: proc(t: ^testing.T) { msg_str := string(hex.encode(msg, context.temp_allocator)) dst_str := string(hex.encode(dst[:tag_len], context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == expected_str, - fmt.tprintf( - "%s/incremental: Expected: %s for input of %s - %s, but got %s instead", - algo_name, - tags_sha256[i], - key_str, - msg_str, - dst_str, - ), + "%s/incremental: Expected: %s for input of %s - %s, but got %s instead", + algo_name, + tags_sha256[i], + key_str, + msg_str, + dst_str, ) hmac.sum(algo, dst, msg, key) oneshot_str := string(hex.encode(dst[:tag_len], context.temp_allocator)) - tc.expect( + testing.expectf( t, oneshot_str == expected_str, - fmt.tprintf( - "%s/oneshot: Expected: %s for input of %s - %s, but got %s instead", - algo_name, - tags_sha256[i], - key_str, - msg_str, - oneshot_str, - ), + "%s/oneshot: Expected: %s for input of %s - %s, but got %s instead", + algo_name, + tags_sha256[i], + key_str, + msg_str, + oneshot_str, ) } } @(test) test_poly1305 :: proc(t: ^testing.T) { - tc.log(t, "Testing poly1305") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() // Test cases taken from poly1305-donna. key := [poly1305.KEY_SIZE]byte { @@ -157,16 +140,17 @@ test_poly1305 :: proc(t: ^testing.T) { // Verify - oneshot + compare ok := poly1305.verify(tag[:], msg[:], key[:]) - tc.expect(t, ok, "oneshot verify call failed") + testing.expect(t, ok, "oneshot verify call failed") // Sum - oneshot derived_tag: [poly1305.TAG_SIZE]byte poly1305.sum(derived_tag[:], msg[:], key[:]) derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_tag_str == tag_str, - fmt.tprintf("Expected %s for sum(msg, key), but got %s instead", tag_str, derived_tag_str), + "Expected %s for sum(msg, key), but got %s instead", + tag_str, derived_tag_str, ) // Incremental @@ -182,21 +166,16 @@ test_poly1305 :: proc(t: ^testing.T) { } poly1305.final(&ctx, derived_tag[:]) derived_tag_str = string(hex.encode(derived_tag[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_tag_str == tag_str, - fmt.tprintf( - "Expected %s for init/update/final - incremental, but got %s instead", - tag_str, - derived_tag_str, - ), + "Expected %s for init/update/final - incremental, but got %s instead", + tag_str, derived_tag_str, ) } @(test) test_siphash_2_4 :: proc(t: ^testing.T) { - tc.log(t, "Testing SipHash-2-4") - // Test vectors from // https://github.com/veorq/SipHash/blob/master/vectors.h test_vectors := [?]u64 { @@ -225,6 +204,7 @@ test_siphash_2_4 :: proc(t: ^testing.T) { for i in 0 ..< len(test_vectors) { data := make([]byte, i) + defer delete(data) for j in 0 ..< i { data[j] = byte(j) } @@ -232,15 +212,13 @@ test_siphash_2_4 :: proc(t: ^testing.T) { vector := test_vectors[i] computed := siphash.sum_2_4(data[:], key[:]) - tc.expect( + testing.expectf( t, computed == vector, - fmt.tprintf( - "Expected: 0x%x for input of %v, but got 0x%x instead", - vector, - data, - computed, - ), + "Expected: 0x%x for input of %v, but got 0x%x instead", + vector, + data, + computed, ) } } diff --git a/tests/core/crypto/test_core_crypto_sha3_variants.odin b/tests/core/crypto/test_core_crypto_sha3_variants.odin index 8e44996bc..c11868e72 100644 --- a/tests/core/crypto/test_core_crypto_sha3_variants.odin +++ b/tests/core/crypto/test_core_crypto_sha3_variants.odin @@ -2,30 +2,14 @@ package test_core_crypto import "base:runtime" import "core:encoding/hex" -import "core:fmt" import "core:testing" - import "core:crypto/kmac" import "core:crypto/shake" import "core:crypto/tuplehash" -import tc "tests:common" - -@(test) -test_sha3_variants :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - tc.log(t, "Testing SHA3 derived functions") - - test_shake(t) - test_cshake(t) - test_tuplehash(t) - test_kmac(t) -} - @(test) test_shake :: proc(t: ^testing.T) { - tc.log(t, "Testing SHAKE") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() test_vectors := []struct { sec_strength: int, @@ -67,23 +51,21 @@ test_shake :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.output, - fmt.tprintf( - "SHAKE%d: Expected: %s for input of %s, but got %s instead", - v.sec_strength, - v.output, - v.str, - dst_str, - ), + "SHAKE%d: Expected: %s for input of %s, but got %s instead", + v.sec_strength, + v.output, + v.str, + dst_str, ) } } @(test) test_cshake :: proc(t: ^testing.T) { - tc.log(t, "Testing cSHAKE") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() test_vectors := []struct { sec_strength: int, @@ -135,29 +117,27 @@ test_cshake :: proc(t: ^testing.T) { shake.init_cshake_256(&ctx, domainsep) } - data, _ := hex.decode(transmute([]byte)(v.str)) + data, _ := hex.decode(transmute([]byte)(v.str), context.temp_allocator) shake.write(&ctx, data) shake.read(&ctx, dst) dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.output, - fmt.tprintf( - "cSHAKE%d: Expected: %s for input of %s, but got %s instead", - v.sec_strength, - v.output, - v.str, - dst_str, - ), + "cSHAKE%d: Expected: %s for input of %s, but got %s instead", + v.sec_strength, + v.output, + v.str, + dst_str, ) } } @(test) test_tuplehash :: proc(t: ^testing.T) { - tc.log(t, "Testing TupleHash(XOF)") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() test_vectors := []struct { sec_strength: int, @@ -317,7 +297,7 @@ test_tuplehash :: proc(t: ^testing.T) { } for e in v.tuple { - data, _ := hex.decode(transmute([]byte)(e)) + data, _ := hex.decode(transmute([]byte)(e), context.temp_allocator) tuplehash.write_element(&ctx, data) } @@ -332,24 +312,22 @@ test_tuplehash :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.output, - fmt.tprintf( - "TupleHash%s%d: Expected: %s for input of %v, but got %s instead", - suffix, - v.sec_strength, - v.output, - v.tuple, - dst_str, - ), + "TupleHash%s%d: Expected: %s for input of %v, but got %s instead", + suffix, + v.sec_strength, + v.output, + v.tuple, + dst_str, ) } } @(test) test_kmac :: proc(t:^testing.T) { - tc.log(t, "Testing KMAC") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() test_vectors := []struct { sec_strength: int, @@ -410,7 +388,7 @@ test_kmac :: proc(t:^testing.T) { for v in test_vectors { dst := make([]byte, len(v.output) / 2, context.temp_allocator) - key, _ := hex.decode(transmute([]byte)(v.key)) + key, _ := hex.decode(transmute([]byte)(v.key), context.temp_allocator) domainsep := transmute([]byte)(v.domainsep) ctx: kmac.Context @@ -421,24 +399,22 @@ test_kmac :: proc(t:^testing.T) { kmac.init_256(&ctx, key, domainsep) } - data, _ := hex.decode(transmute([]byte)(v.msg)) + data, _ := hex.decode(transmute([]byte)(v.msg), context.temp_allocator) kmac.update(&ctx, data) kmac.final(&ctx, dst) dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.output, - fmt.tprintf( - "KMAC%d: Expected: %s for input of (%s, %s, %s), but got %s instead", - v.sec_strength, - v.output, - v.key, - v.domainsep, - v.msg, - dst_str, - ), + "KMAC%d: Expected: %s for input of (%s, %s, %s), but got %s instead", + v.sec_strength, + v.output, + v.key, + v.domainsep, + v.msg, + dst_str, ) } -} +} \ No newline at end of file diff --git a/tests/core/crypto/test_crypto_benchmark.odin b/tests/core/crypto/test_crypto_benchmark.odin deleted file mode 100644 index cc69cb16d..000000000 --- a/tests/core/crypto/test_crypto_benchmark.odin +++ /dev/null @@ -1,301 +0,0 @@ -package test_core_crypto - -import "base:runtime" -import "core:encoding/hex" -import "core:fmt" -import "core:testing" -import "core:time" - -import "core:crypto/chacha20" -import "core:crypto/chacha20poly1305" -import "core:crypto/ed25519" -import "core:crypto/poly1305" -import "core:crypto/x25519" - -import tc "tests:common" - -// Cryptographic primitive benchmarks. - -@(test) -bench_crypto :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - fmt.println("Starting benchmarks:") - - bench_chacha20(t) - bench_poly1305(t) - bench_chacha20poly1305(t) - bench_ed25519(t) - bench_x25519(t) -} - -_setup_sized_buf :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - assert(options != nil) - - options.input = make([]u8, options.bytes, allocator) - return nil if len(options.input) == options.bytes else .Allocation_Error -} - -_teardown_sized_buf :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - assert(options != nil) - - delete(options.input) - return nil -} - -_benchmark_chacha20 :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - buf := options.input - key := [chacha20.KEY_SIZE]byte { - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - } - nonce := [chacha20.NONCE_SIZE]byte { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - } - - ctx: chacha20.Context = --- - chacha20.init(&ctx, key[:], nonce[:]) - - for _ in 0 ..= options.rounds { - chacha20.xor_bytes(&ctx, buf, buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - return nil -} - -_benchmark_poly1305 :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - buf := options.input - key := [poly1305.KEY_SIZE]byte { - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - } - - tag: [poly1305.TAG_SIZE]byte = --- - for _ in 0 ..= options.rounds { - poly1305.sum(tag[:], buf, key[:]) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - //options.hash = u128(h) - return nil -} - -_benchmark_chacha20poly1305 :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - buf := options.input - key := [chacha20.KEY_SIZE]byte { - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - } - nonce := [chacha20.NONCE_SIZE]byte { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - } - - tag: [chacha20poly1305.TAG_SIZE]byte = --- - - for _ in 0 ..= options.rounds { - chacha20poly1305.encrypt(buf, tag[:], key[:], nonce[:], nil, buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - return nil -} - -benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) { - fmt.printf( - "\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", - name, - options.rounds, - options.processed, - time.duration_nanoseconds(options.duration), - options.rounds_per_second, - options.megabytes_per_second, - ) -} - -bench_chacha20 :: proc(t: ^testing.T) { - name := "ChaCha20 64 bytes" - options := &time.Benchmark_Options { - rounds = 1_000, - bytes = 64, - setup = _setup_sized_buf, - bench = _benchmark_chacha20, - teardown = _teardown_sized_buf, - } - - err := time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "ChaCha20 1024 bytes" - options.bytes = 1024 - err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "ChaCha20 65536 bytes" - options.bytes = 65536 - err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) - benchmark_print(name, options) -} - -bench_poly1305 :: proc(t: ^testing.T) { - name := "Poly1305 64 zero bytes" - options := &time.Benchmark_Options { - rounds = 1_000, - bytes = 64, - setup = _setup_sized_buf, - bench = _benchmark_poly1305, - teardown = _teardown_sized_buf, - } - - err := time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "Poly1305 1024 zero bytes" - options.bytes = 1024 - err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) - benchmark_print(name, options) -} - -bench_chacha20poly1305 :: proc(t: ^testing.T) { - name := "chacha20poly1305 64 bytes" - options := &time.Benchmark_Options { - rounds = 1_000, - bytes = 64, - setup = _setup_sized_buf, - bench = _benchmark_chacha20poly1305, - teardown = _teardown_sized_buf, - } - - err := time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "chacha20poly1305 1024 bytes" - options.bytes = 1024 - err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "chacha20poly1305 65536 bytes" - options.bytes = 65536 - err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) - benchmark_print(name, options) -} - -bench_ed25519 :: proc(t: ^testing.T) { - iters :: 10000 - - priv_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" - priv_bytes, _ := hex.decode(transmute([]byte)(priv_str), context.temp_allocator) - priv_key: ed25519.Private_Key - start := time.now() - for i := 0; i < iters; i = i + 1 { - ok := ed25519.private_key_set_bytes(&priv_key, priv_bytes) - assert(ok, "private key should deserialize") - } - elapsed := time.since(start) - tc.log( - t, - fmt.tprintf( - "ed25519.private_key_set_bytes: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ), - ) - - pub_bytes := priv_key._pub_key._b[:] // "I know what I am doing" - pub_key: ed25519.Public_Key - start = time.now() - for i := 0; i < iters; i = i + 1 { - ok := ed25519.public_key_set_bytes(&pub_key, pub_bytes[:]) - assert(ok, "public key should deserialize") - } - elapsed = time.since(start) - tc.log( - t, - fmt.tprintf( - "ed25519.public_key_set_bytes: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ), - ) - - msg := "Got a job for you, 621." - sig_bytes: [ed25519.SIGNATURE_SIZE]byte - msg_bytes := transmute([]byte)(msg) - start = time.now() - for i := 0; i < iters; i = i + 1 { - ed25519.sign(&priv_key, msg_bytes, sig_bytes[:]) - } - elapsed = time.since(start) - tc.log(t, fmt.tprintf("ed25519.sign: ~%f us/op", time.duration_microseconds(elapsed) / iters)) - - start = time.now() - for i := 0; i < iters; i = i + 1 { - ok := ed25519.verify(&pub_key, msg_bytes, sig_bytes[:]) - assert(ok, "signature should validate") - } - elapsed = time.since(start) - tc.log( - t, - fmt.tprintf("ed25519.verify: ~%f us/op", time.duration_microseconds(elapsed) / iters), - ) -} - -bench_x25519 :: proc(t: ^testing.T) { - point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" - scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" - - point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator) - scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator) - out: [x25519.POINT_SIZE]byte = --- - - iters :: 10000 - start := time.now() - for i := 0; i < iters; i = i + 1 { - x25519.scalarmult(out[:], scalar[:], point[:]) - } - elapsed := time.since(start) - - tc.log( - t, - fmt.tprintf("x25519.scalarmult: ~%f us/op", time.duration_microseconds(elapsed) / iters), - ) -} diff --git a/tests/core/download_assets.py b/tests/core/download_assets.py index 7874b7e91..fc4a71cdc 100644 --- a/tests/core/download_assets.py +++ b/tests/core/download_assets.py @@ -7,8 +7,8 @@ import zipfile import hashlib import hmac -TEST_SUITES = ['PNG', 'XML'] -DOWNLOAD_BASE_PATH = "assets/{}" +TEST_SUITES = ['PNG', 'XML', 'BMP'] +DOWNLOAD_BASE_PATH = sys.argv[1] + "/{}" ASSETS_BASE_URL = "https://raw.githubusercontent.com/odin-lang/test-assets/master/{}/{}" HMAC_KEY = "https://odin-lang.org" HMAC_HASH = hashlib.sha3_512 @@ -192,6 +192,94 @@ HMAC_DIGESTS = { 'z06n2c08.png': "94268c1998de1f4304d24219e31175def7375cc26e2bbfc7d1ac20465a42fae49bcc8ff7626873138b537588e8bce21b6d5e1373efaade1f83cae455334074aa", 'z09n2c08.png': "3cbb1bb58d78ecc9dd5568a8e9093ba020b63449ef3ab102f98fac4220fc9619feaa873336a25f3c1ad99cfb3e5d32bcfe52d966bc8640d1d5ba4e061741743e", + 'ba-bm.bmp': "2f76d46b1b9bea62e08e7fc5306452a495616cb7af7a0cbb79237ed457b083418d5859c9e6cfd0d9fbf1fe24495319b6f206135f36f2bd19330de01a8eaf20c8", + 'badbitcount.bmp': "2d37e22aa2e659416c950815841e5a402f2e9c21eb677390fc026eefaeb5be64345a7ef0fac2965a2cae8abe78c1e12086a7d93d8e62cc8659b35168c82f6d5f", + 'badbitssize.bmp': "f59cc30827bcb56f7e946dcffcaab22a5e197f2e3884cf80a2e596f5653f5203b3927674d9d5190486239964e65228f4e3f359cdd2f7d061b09846f5f26bfaa9", + 'baddens1.bmp': "aa84bebc41b3d50329269da9ee61fd7e1518ffd0e8f733af6872323bc46ace6ed1c9931a65a367d97b8b2cb2aa772ccd94fd3def0a79fd1c0baf185d669c386f", + 'baddens2.bmp': "5c254a8cde716fae77ebf20294a404383fd6afc705d783c5418762e7c4138aa621625bc6d08a8946ee3f1e8c40c767681a39806735bb3b3026fee5eb91d8fadc", + 'badfilesize.bmp': "9019b6853a91f69bd246f9b28da47007aec871c0e46fea7cd6ab5c30460a6938a1b09da8fa7ba8895650e37ce14a79d4183e9f2401eb510f60455410e2266eb5", + 'badheadersize.bmp': "90412d7c3bff7336d5e0c7ae899d8a53b82235072034f00783fb2403479447cd2959644f7ec70ae0988f99cc49b63356c8710b808ddd2280e19dca484f34074e", + 'badpalettesize.bmp': "d914a89f7b78fcdd6ab4433c176355755687b65c3cfc23db57de9d04447c440fa31d993db184940c1dc09b37e8e044324d8237877d3d1b1ad5657c4929d8435a", + 'badplanes.bmp': "46f583d4a43ef0c9964765b9d8820369955f0568a4eae0bf215434f508e8e03457bd759b73c344c2f88de7f33fc5379517ce3cf5b2e5a16ebc20c05df73aa723", + 'badrle.bmp': "a64e1551fd60159ff469ce25e1f5b4575dc462684f4ff66c7ea69b2990c7c9d2547b72237020e2d001f69dfd31f1ac45e0a9630d0ddd11c77584881f3e25609e", + 'badrle4.bmp': "2bd22418010b1ac3eac50932ed06e578411ac2741bfa50a9edd1b360686efa28c74df8b14d92e05b711eeb88a5e826256c6a5cf5a0176a29369fb92b336efb93", + 'badrle4bis.bmp': "d7a24ab095e1ca5e888dd1bcb732b19bb1983f787c64c1eb5a273da0f58c4b8cd137197df9ac47572a74c3026aab5af1f08551a2121af37b8941cffa71df1951", + 'badrle4ter.bmp': "825cc5361378d44524205b117825f95228c4d093d39ac2fc2ab755be743df78784529f2019418deca31059f3e46889a66658e7424b4f896668ee4cfa281574bc", + 'badrlebis.bmp': "f41acfd4f989302bb5ec42a2e759a56f71a5ecac5a814842e32542742ca015464f8579ebeec0e7e9cea45e2aafe51456cfe18b48b509bc3704f992bcc9d321af", + 'badrleter.bmp': "a8f3e0b0668fc4f43353028d5fca87d6cac6ff0c917c4e7a61c624918360ff598ec9eaa32f5c6a070da9bf6e90c58426f5a901fdab9dfb0a4fdca0c72ba67de4", + 'badwidth.bmp': "68f192a55b8de66f8e13fe316647262a5e4641365eb77d4987c84ab1eae35b7cba20827277cd569583543819de70ec75f383367f72cd229e48743ad1e45bfa9e", + 'pal1.bmp': "0194c9b501ac7e043fab78746e6f142e0c880917d0fd6dbb7215765b8fc1ce4403ad85146c555665ba6e37d3b47edad5e687b9260e7a61a27d8a059bc81bb525", + 'pal1bg.bmp': "3aafc29122bd6e97d88d740be1f61cb9febe8373d19ae6d731f4af776c868dd489260287bf0cf1c960f9d9afcbc7448e83e45435d3e42e913823c0f5c2a80d9f", + 'pal1huffmsb.bmp': "4e122f602c3556f4f5ab45f9e13a617d8210d81f587d08cbd6c2110dc6231573aec92a6344aeb4734c00d3dcf380130f53a887002756811d8edd6bc5aabbafc0", + 'pal1p1.bmp': "33e2b2b1c1bed43ba64888d7739eb830c7789857352513de08b6e35718ac0e421afcdae0e7bab97c25d1ad972eb4f09e2c6556c416d4d7367c545330c4123df0", + 'pal1wb.bmp': "bc583ad4eaae40f5d2e3a6280aeb3c62ee11b2cf05ba7c8386f9578587e29b66819293992bdcd31c2750c21cd9bf97daa603ce1051fbfdd40fadbc1860156853", + 'pal2.bmp': "7b560ba972cf58ca1ed01910fa4f630ca74e657d46d134e2ac0df733eb5773d0a1788e745d5240efa18f182bd8dce22c7ac7cee6f99ddc946a27b65297762764", + 'pal2color.bmp': "b868a8aaa22fac3aa86bbd5270eb5ffee06959461be8177880829d838be0391d9617d11d73fab1643520a92364dc333c25c0510bb2628c8fb945719518d2675f", + 'pal4.bmp': "53a39fdb86630c828d9003a1e95dbd59c47524c4ec044d8ce72e1b643166b4c2b6ec06ab5191cb25d17be2fcb18bd7a9e0b7ec169722e6d89b725609a15b1df1", + 'pal4gs.bmp': "ab4c2078943afdf19bcc02b1ebbe5a69cfa93d1152f7882db6176c39b917191a2760fbb2127e5207b0bfb3dafd711593a6aed61d312807605913062aa1ce9c2f", + 'pal4rle.bmp': "c86c86280b75a252ccf484e4bba2df45d3747dc1e4879795e925613959a0c451e2fc4890532e8aef9911e38e45e7d6a8baf29d57e573d26c20923a5823700443", + 'pal4rlecut.bmp': "f38d485dbb8e67bdeaefba181f9a05556a986ed3f834edca723c088e813764bb2b42240d4fbb938a1775370b79b9ea2f14277ffe9c7247c1e0e77766fec27189", + 'pal4rletrns.bmp': "b81e7fed38854d201a3199ce50ca05e92ca287c860797142857ac20b4a2f28952b058e21687c0fae60712f5784cd2c950ce70148ba1316efe31d4f3fc4006817", + 'pal8-0.bmp': "f39a4f1827c52bb620d975f8c72f5e95f90ac6c65ae0a6776ff1ad95808c090de17cbd182188a85157396fd9649ea4b5d84bb7c9175ab49ce2845da214c16bff", + 'pal8.bmp': "be27e55a866cbb655fdd917435cd6a5b62c20ae0d6ef7c1533c5a01dd9a893f058cc4ba2d902ab9315380009808e06b7f180116c9b790587cf62aa770c7a4a07", + 'pal8badindex.bmp': "bd5fc036985ae705182915a560dee2e5dfb3bd8b50932337b9085e190259c66e6bae5fbc813a261d352a60dcb0755798bdc251d6c2a0b638a7e337ba58811811", + 'pal8gs.bmp': "228f944b3e45359f62a2839d4e7b94d7f3a1074ad9e25661fdb9e8fff4c15581c85a7bb0ac75c92b95c7537ececc9d80b835cfe55bc7560a513118224a9ed36f", + 'pal8nonsquare.bmp': "b8adc9b03880975b232849ea1e8f87705937929d743df3d35420902b32557065354ab71d0d8176646bf0ad72c583c884cfcd1511017260ffca8c41d5a358a3eb", + 'pal8offs.bmp': "c92f1e1835d753fd8484be5198b2b8a2660d5e54117f6c4fc6d2ebc8d1def72a8d09cd820b1b8dcee15740b47151d62b8b7aca0b843e696252e28226b51361cf", + 'pal8os2-hs.bmp': "477c04048787eb412f192e7fe47ae96f14d7995391e78b10cc4c365f8c762f60c54cad7ef9d1705a78bd490a578fb346ee0a383c3a3fdf790558a12589eb04eb", + 'pal8os2-sz.bmp': "fd0eeb733be9b39f492d0f67dd28fc67207149e41691c206d4de4c693b5dea9458b88699a781383e7050a3b343259659aae64fec0616c98f3f8555cbf5c9e46c", + 'pal8os2.bmp': "cdab3ed7bc9f38d89117332a21418b3c916a99a8d8fb6b7ce456d54288c96152af12c0380293b04e96594a7867b83be5c99913d224c9750c7d38295924e0735a", + 'pal8os2sp.bmp': "f6e595a6db992ab7d1f79442d31f39f648061e7de13e51b07933283df065ce405c0208e6101ac916e4eb0613e412116f008510021a2d17543aa7f0a32349c96f", + 'pal8os2v2-16.bmp': "f52877d434218aa6b772a7aa0aaba4c2ae6ce35ecfa6876943bb350fdf9554f1f763a8d5bb054362fb8f9848eb71ce14a371f4a76da4b9475cdcee4f286109a4", + 'pal8os2v2-40sz.bmp': "9481691ada527df1f529316d44b5857c6a840c5dafa7e9795c9cb92dac02c6cc35739d3f6ce33d4ab6ff6bcd6b949741e89dc8c42cf52ad4546ff58cd3b5b66a", + 'pal8os2v2-sz.bmp': "99cd2836f90591cd27b0c8696ecff1e7a1debcef284bbe5d21e68759270c1bfe1f32ee8f576c49f3e64d8f4e4d9096574f3c8c79bfdae0545689da18364de3e7", + 'pal8os2v2.bmp': "7859b265956c7d369db7a0a357ce09bcda74e98954de88f454cae5e7cb021222146687a7770ce0cc2c58f1439c7c21c45c0c27464944e73913e1c88afc836c8a", + 'pal8oversizepal.bmp': "e778864e0669a33fce27c0ccd5b6460b572a5db01975e8d56acec8a9447e1c58d6051ad3516cfa96a39f4eb7f2576154188ea62ec187bcf4ae323883499383c0", + 'pal8rle.bmp': "88942a1cd2e36d1e0f0e2748a888034057919c7ec0f8d9b2664deb1daf1a6e45ed3e722dff5d810f413d6fc182e700a16d6563dd25f67dc6d135d751cd736dea", + 'pal8rlecut.bmp': "cda9fa274cde590aeaca81516e0465684cfae84e934eb983301801e978e6e2e9c590d22af992d9912e51bb9c2761945276bdbe0b6c47f3a021514414e1f3f455", + 'pal8rletrns.bmp': "0b2d5618dc9c81caa72c070070a4245dd9cd3de5d344b76ce9c15d0eeb72e2675efc264201f8709dfcffd234df09e76d6f328f16f2ad873ba846f870cadfa486", + 'pal8topdown.bmp': "d470a2b7556fa88eac820427cb900f59a121732cdb4a7f3530ed457798139c946a884a34ab79d822feb84c2ca6f4d9a65f6e792994eafc3a189948b9e4543546", + 'pal8v4.bmp': "0382610e32c49d5091a096cb48a54ebbf44d9ba1def96e2f30826fd3ddf249f5aed70ca5b74c484b6cdc3924f4d4bfed2f5194ad0bcf1d99bfaa3a619e299d86", + 'pal8v5.bmp': "50fadaa93aac2a377b565c4dc852fd4602538863b913cb43155f5ad7cf79928127ca28b33e5a3b0230076ea4a6e339e3bf57f019333f42c4e9f003a8f2376325", + 'pal8w124.bmp': "e54a901b9badda655cad828d226b381831aea7e36aec8729147e9e95a9f2b21a9d74d93756e908e812902a01197f1379fe7e35498dbafed02e27c853a24097b7", + 'pal8w125.bmp': "d7a04d45ef5b3830da071ca598f1e2a413c46834968b2db7518985cf8d8c7380842145899e133e71355b6b7d040ee9e97adec1e928ce4739282e0533058467c0", + 'pal8w126.bmp': "4b93845a98797679423c669c541a248b4cdfee80736f01cec29d8b40584bf55a27835c80656a2bf5c7ad3ed211c1f7d3c7d5831a6726904b39f10043a76b658d", + 'reallybig.bmp': "babbf0335bac63fd2e95a89210c61ae6bbaaeeab5f07974034e76b4dc2a5c755f77501e3d056479357445aac442e2807d7170ec44067bab8fd35742f0e7b8440", + 'rgb16-231.bmp': "611a87cb5d29f16ef71971714a3b0e3863a6df51fff16ce4d4df8ee028442f9ce03669fb5d7a6a838a12a75d8a887b56b5a2e44a3ad62f4ef3fc2e238c33f6a1", + 'rgb16-3103.bmp': "7fdff66f4d94341da522b4e40586b3b8c327be9778e461bca1600e938bfbaa872b484192b35cd84d9430ca20aa922ec0301567a74fb777c808400819db90b09d", + 'rgb16-565.bmp': "777883f64b9ae80d77bf16c6d062082b7a4702f8260c183680afee6ec26e48681bcca75f0f81c470be1ac8fcb55620b3af5ce31d9e114b582dfd82300a3d6654", + 'rgb16-565pal.bmp': "57e9dcf159415b3574a1b343a484391b0859ab2f480e22157f2a84bc188fde141a48826f960c6f30b1d1f17ef6503ec3afc883a2f25ff09dd50c437244f9ae7f", + 'rgb16-880.bmp': "8d61183623002da4f7a0a66b42aa58a120e3a91578bb0c4a5e2c5ba7d08b875d43a22f2b5b3a449d3caf4cc303cb05111dd1d5169953f288493b7ea3c2423d24", + 'rgb16.bmp': "1c0fe56661d4998edd76efedda520a441171d42ae4dad95b350e3b61deda984c3a3255392481fe1903e5e751357da3f35164935e323377f015774280036ba39e", + 'rgb16bfdef.bmp': "ed55d086e27ba472076df418be0046b740944958afeb84d05aa2bbe578dec27ced122ffefb6d549e1d07e05eb608979b3ac9b1bd809f8237cf0984ffdaa24716", + 'rgb16faketrns.bmp': "9cd4a8e05fe125649e360715851ef912e78a56d30e0ea1b1cfb6eaafd386437d45de9c1e1a845dd8d63ff5a414832355b8ae0e2a96d72a42a7205e8a2742d37c", + 'rgb24.bmp': "4f0ce2978bbfea948798b2fdcc4bdbe8983a6c94d1b7326f39daa6059368e08ebf239260984b64eeb0948f7c8089a523e74b7fa6b0437f9205d8af8891340389", + 'rgb24largepal.bmp': "b377aee1594c5d9fc806a70bc62ee83cf8d1852b4a2b18fd3e9409a31aa3b5a4cf5e3b4af2cbdebcef2b5861b7985a248239684a72072437c50151adc524e9df", + 'rgb24pal.bmp': "f40bb6e01f6ecb3d55aa992bf1d1e2988ea5eb11e3e58a0c59a4fea2448de26f231f45e9f378b7ee1bdd529ec57a1de38ea536e397f5e1ac6998921e066ab427", + 'rgb24png.bmp': "c60890bbd79c12472205665959eb6e2dd2103671571f80117b9e963f897cffca103181374a4797f53c7768af01a705e830a0de4dd8fab7241d24c17bee4a4dbe", + 'rgb24rle24.bmp': "ea0ff3f512dd04176d14b43dfbee73ac7f1913aa1b77587e187e271781c7dacec880cec73850c4127ea9f8dd885f069e281f159bb5232e93cbb2d1ee9cb50438", + 'rgb32-111110.bmp': "732884e300d4edbcf31556a038947beefc0c5e749131a66d2d7aa1d4ec8c8eba07833133038a03bbe4c4fa61a805a5df1f797b5853339ee6a8140478f5f70a76", + 'rgb32-7187.bmp': "4c55aab2e4ecf63dc30b04e5685c5d9fba7354ca0e1327d7c4b15d6da10ac66ca1cea6f0303f9c5f046da3bcd2566275384e0e7bb14fcc5196ec39ca90fac194", + 'rgb32-xbgr.bmp': "1e9f240eaec6ac2833f8c719f1fb53cc7551809936620e871ccacfab26402e1afc6503b9f707e4ec25f15529e3ce6433c7f999d5714af31dfb856eb67e772f64", + 'rgb32.bmp': "32033dbf9462e5321b1182ba739624ed535aa4d33b775ffeeaf09d2d4cb663e4c3505e8c05489d940f754dde4b50a2e0b0688b21d06755e717e6e511b0947525", + 'rgb32bf.bmp': "7243c215827a9b4a1d7d52d67fb04ffb43b0b176518fbdab43d413e2a0c18883b106797f1acd85ba68d494ec939b0caab8789564670d058caf0e1175ce7983fb", + 'rgb32bfdef.bmp': "a81433babb67ce714285346a77bfccd19cf6203ac1d8245288855aff20cf38146a783f4a7eac221db63d1ee31345da1329e945b432f0e7bcf279ea88ff5bb302", + 'rgb32fakealpha.bmp': "abecaf1b5bfad322de7aec897efe7aa6525f2a77a0af86cc0a0a366ed1650da703cf4b7b117a7ba34f21d03a8a0045e5821248cdefa00b0c78e01d434b55e746", + 'rgb32h52.bmp': "707d734393c83384bc75720330075ec9ffefc69167343259ebf95f9393948364a40f33712619f962e7056483b73334584570962c16da212cd5291f764b3f2cd1", + 'rgba16-1924.bmp': "3e41a5d8d951bac580c780370ca21e0783de8154f4081106bc58d1185bb2815fc5b7f08f2a1c75cd205fc52c888e9d07c91856651603a2d756c9cfc392585598", + 'rgba16-4444.bmp': "a6662186b23bd434a7e019d2a71cd95f53a47b64a1afea4c27ae1120685d041a9ff98800a43a9d8f332682670585bdb2fa77ff77b6def65139fe725323f91561", + 'rgba16-5551.bmp': "a7d9f8ae7f8349cd6df651ab9d814411939fa2a235506ddfdd0df5a8f8abcf75552c32481ea035ff29e683bdcd34da68eb23730857e0716b79af51d69a60757b", + 'rgba32-1.bmp': "3958d18d2a7f32ada69cb11e0b4397821225a5c40acc7b6d36ff28ee4565e150cc508971278a2ddf8948aaff86f66ec6a0c24513db44962d81b79c4239b3e612", + 'rgba32-1010102.bmp': "59a28db5563caf954d31b20a1d1cc59366fcfd258b7ba2949f7281978460a3d94bedcc314c089243dd7463bb18d36a9473355158a7d903912cb25b98eab6b068", + 'rgba32-2.bmp': "9b7e5965ff9888f011540936ab6b3022edf9f6c5d7e541d6882cb81820cf1d68326d65695a6f0d01999ac10a496a504440906aa45e413da593405563c54c1a05", + 'rgba32-61754.bmp': "784ae0a32b19fa925e0c86dbff3bd38d80904d0fa7dc3b03e9d4f707d42b1604c1f54229e901ccc249cab8c2976d58b1e16980157d9bf3dbc4e035e2b2fd1779", + 'rgba32-81284.bmp': "fcfca645017c0d15d44b08598a90d238d063763fd06db665d9a8e36ef5099ce0bf4d440e615c6d6b1bf99f38230d4848318bfa1e6d9bfdd6dfd521cc633ba110", + 'rgba32abf.bmp': "2883d676966d298d235196f102e044e52ef18f3cb5bb0dd84738c679f0a1901181483ca2df1cccf6e4b3b4e98be39e81de69c9a58f0d70bc3ebb0fcea80daa0c", + 'rgba32h56.bmp': "507d0caf29ccb011c83c0c069c21105ea1d58e06b92204f9c612f26102123a7680eae53fef023c701952d903e11b61f8aa07618c381ea08f6808c523f5a84546", + 'rgba64.bmp': "d01f14f649c1c33e3809508cc6f089dd2ab0a538baf833a91042f2e54eca3f8e409908e15fa8763b059d7fa022cf5c074d9f5720eed5293a4c922e131c2eae68", + 'rletopdown.bmp': "37500893aad0b40656aa80fd5c7c5f9b35d033018b8070d8b1d7baeb34c90f90462288b13295204b90aa3e5c9be797d22a328e3714ab259334e879a09a3de175", + 'shortfile.bmp': "be3ffade7999304f00f9b7d152b5b27811ad1166d0fd43004392467a28f44b6a4ec02a23c0296bacd4f02f8041cd824b9ca6c9fc31fed27e36e572113bb47d73", + 'unicode.xml': "e0cdc94f07fdbb15eea811ed2ae6dcf494a83d197dafe6580c740270feb0d8f5f7146d4a7d4c2d2ea25f8bd9678bc986123484b39399819a6b7262687959d1ae", } @@ -233,6 +321,7 @@ def try_download_and_unpack_zip(suite): hmac_digest = hmac.new(HMAC_KEY.encode(), file_data, HMAC_HASH).hexdigest() print("{} *{}".format(hmac_digest, file.filename)) + if not hmac.compare_digest(hmac_digest, HMAC_DIGESTS[file.filename]): print("FAIL! Expected: {}".format(HMAC_DIGESTS[file.filename])) return 4 diff --git a/tests/core/encoding/base64/base64.odin b/tests/core/encoding/base64/base64.odin index e48eea020..6679c8ce2 100644 --- a/tests/core/encoding/base64/base64.odin +++ b/tests/core/encoding/base64/base64.odin @@ -1,61 +1,38 @@ package test_encoding_base64 import "base:intrinsics" - import "core:encoding/base64" -import "core:fmt" -import "core:os" -import "core:reflect" import "core:testing" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect_value :: testing.expect_value - -} else { - expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { - TEST_count += 1 - ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) - if !ok { - TEST_fail += 1 - fmt.printf("[%v] expected %v, got %v\n", loc, expected, value) - } - return ok - } +Test :: struct { + vector: string, + base64: string, } -main :: proc() { - t := testing.T{} - - test_encoding(&t) - test_decoding(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } +tests :: []Test{ + {"", ""}, + {"f", "Zg=="}, + {"fo", "Zm8="}, + {"foo", "Zm9v"}, + {"foob", "Zm9vYg=="}, + {"fooba", "Zm9vYmE="}, + {"foobar", "Zm9vYmFy"}, } @(test) test_encoding :: proc(t: ^testing.T) { - expect_value(t, base64.encode(transmute([]byte)string("")), "") - expect_value(t, base64.encode(transmute([]byte)string("f")), "Zg==") - expect_value(t, base64.encode(transmute([]byte)string("fo")), "Zm8=") - expect_value(t, base64.encode(transmute([]byte)string("foo")), "Zm9v") - expect_value(t, base64.encode(transmute([]byte)string("foob")), "Zm9vYg==") - expect_value(t, base64.encode(transmute([]byte)string("fooba")), "Zm9vYmE=") - expect_value(t, base64.encode(transmute([]byte)string("foobar")), "Zm9vYmFy") + for test in tests { + v := base64.encode(transmute([]byte)test.vector) + defer delete(v) + testing.expect_value(t, v, test.base64) + } } @(test) test_decoding :: proc(t: ^testing.T) { - expect_value(t, string(base64.decode("")), "") - expect_value(t, string(base64.decode("Zg==")), "f") - expect_value(t, string(base64.decode("Zm8=")), "fo") - expect_value(t, string(base64.decode("Zm9v")), "foo") - expect_value(t, string(base64.decode("Zm9vYg==")), "foob") - expect_value(t, string(base64.decode("Zm9vYmE=")), "fooba") - expect_value(t, string(base64.decode("Zm9vYmFy")), "foobar") + for test in tests { + v := string(base64.decode(test.base64)) + defer delete(v) + testing.expect_value(t, v, test.vector) + } } diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin index 72244e1d3..d069ef05b 100644 --- a/tests/core/encoding/cbor/test_core_cbor.odin +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -1,105 +1,15 @@ package test_encoding_cbor import "base:intrinsics" - import "core:bytes" import "core:encoding/cbor" import "core:fmt" import "core:io" import "core:math/big" -import "core:mem" -import "core:os" import "core:reflect" import "core:testing" import "core:time" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - expect_value :: testing.expect_value - errorf :: testing.errorf - log :: testing.log - -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - - expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { - TEST_count += 1 - ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) - if !ok { - TEST_fail += 1 - fmt.printf("[%v] expected %v, got %v\n", loc, expected, value) - } - return ok - } - - errorf :: proc(t: ^testing.T, fmts: string, args: ..any, loc := #caller_location) { - TEST_fail += 1 - fmt.printf("[%v] ERROR: ", loc) - fmt.printf(fmts, ..args) - fmt.println() - } - - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - test_marshalling(&t) - - test_marshalling_maybe(&t) - test_marshalling_nil_maybe(&t) - - test_marshalling_union(&t) - - test_lying_length_array(&t) - - test_decode_unsigned(&t) - test_encode_unsigned(&t) - - test_decode_negative(&t) - test_encode_negative(&t) - - test_decode_simples(&t) - test_encode_simples(&t) - - test_decode_floats(&t) - test_encode_floats(&t) - - test_decode_bytes(&t) - test_encode_bytes(&t) - - test_decode_strings(&t) - test_encode_strings(&t) - - test_decode_lists(&t) - test_encode_lists(&t) - - test_decode_maps(&t) - test_encode_maps(&t) - - test_decode_tags(&t) - test_encode_tags(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - Foo :: struct { str: string, cstr: cstring, @@ -143,14 +53,6 @@ FooBars :: bit_set[FooBar; u16] @(test) test_marshalling :: proc(t: ^testing.T) { - tracker: mem.Tracking_Allocator - mem.tracking_allocator_init(&tracker, context.allocator) - context.allocator = mem.tracking_allocator(&tracker) - context.temp_allocator = context.allocator - defer mem.tracking_allocator_destroy(&tracker) - - ev :: expect_value - { nice := "16 is a nice number" now := time.Time{_nsec = 1701117968 * 1e9} @@ -205,18 +107,18 @@ test_marshalling :: proc(t: ^testing.T) { } data, err := cbor.marshal(f, cbor.ENCODE_FULLY_DETERMINISTIC) - ev(t, err, nil) + testing.expect_value(t, err, nil) defer delete(data) decoded, derr := cbor.decode(string(data)) - ev(t, derr, nil) + testing.expect_value(t, derr, nil) defer cbor.destroy(decoded) diagnosis, eerr := cbor.to_diagnostic_format(decoded) - ev(t, eerr, nil) + testing.expect_value(t, eerr, nil) defer delete(diagnosis) - ev(t, diagnosis, `{ + testing.expect_value(t, diagnosis, `{ "base64": 34("MTYgaXMgYSBuaWNlIG51bWJlcg=="), "biggest": 2(h'f951a9fd3c158afdff08ab8e0'), "biggie": 18446744073709551615, @@ -285,7 +187,7 @@ test_marshalling :: proc(t: ^testing.T) { backf: Foo uerr := cbor.unmarshal(string(data), &backf) - ev(t, uerr, nil) + testing.expect_value(t, uerr, nil) defer { delete(backf.str) delete(backf.cstr) @@ -304,104 +206,102 @@ test_marshalling :: proc(t: ^testing.T) { big.destroy(&backf.smallest) } - ev(t, backf.str, f.str) - ev(t, backf.cstr, f.cstr) + testing.expect_value(t, backf.str, f.str) + testing.expect_value(t, backf.cstr, f.cstr) #partial switch v in backf.value { case ^cbor.Map: for entry, i in v { fm := f.value.(^cbor.Map) - ev(t, entry.key, fm[i].key) + testing.expect_value(t, entry.key, fm[i].key) if str, is_str := entry.value.(^cbor.Text); is_str { - ev(t, str^, fm[i].value.(^cbor.Text)^) + testing.expect_value(t, str^, fm[i].value.(^cbor.Text)^) } else { - ev(t, entry.value, fm[i].value) + testing.expect_value(t, entry.value, fm[i].value) } } - case: errorf(t, "wrong type %v", v) + case: testing.expectf(t, false, "wrong type %v", v) } - ev(t, backf.neg, f.neg) - ev(t, backf.iamint, f.iamint) - ev(t, backf.base64, f.base64) - ev(t, backf.renamed, f.renamed) - ev(t, backf.now, f.now) - ev(t, backf.nowie, f.nowie) - for e, i in f.child.dyn { ev(t, backf.child.dyn[i], e) } - for key, value in f.child.mappy { ev(t, backf.child.mappy[key], value) } - ev(t, backf.child.my_integers, f.child.my_integers) - ev(t, len(backf.my_bytes), 0) - ev(t, len(backf.my_bytes), len(f.my_bytes)) - ev(t, backf.ennie, f.ennie) - ev(t, backf.ennieb, f.ennieb) - ev(t, backf.quat, f.quat) - ev(t, backf.comp, f.comp) - ev(t, backf.important, f.important) - ev(t, backf.no, nil) - ev(t, backf.nos, nil) - ev(t, backf.yes, f.yes) - ev(t, backf.biggie, f.biggie) - ev(t, backf.smallie, f.smallie) - ev(t, backf.onetwenty, f.onetwenty) - ev(t, backf.small_onetwenty, f.small_onetwenty) - ev(t, backf.ignore_this, nil) + testing.expect_value(t, backf.neg, f.neg) + testing.expect_value(t, backf.iamint, f.iamint) + testing.expect_value(t, backf.base64, f.base64) + testing.expect_value(t, backf.renamed, f.renamed) + testing.expect_value(t, backf.now, f.now) + testing.expect_value(t, backf.nowie, f.nowie) + for e, i in f.child.dyn { testing.expect_value(t, backf.child.dyn[i], e) } + for key, value in f.child.mappy { testing.expect_value(t, backf.child.mappy[key], value) } + testing.expect_value(t, backf.child.my_integers, f.child.my_integers) + testing.expect_value(t, len(backf.my_bytes), 0) + testing.expect_value(t, len(backf.my_bytes), len(f.my_bytes)) + testing.expect_value(t, backf.ennie, f.ennie) + testing.expect_value(t, backf.ennieb, f.ennieb) + testing.expect_value(t, backf.quat, f.quat) + testing.expect_value(t, backf.comp, f.comp) + testing.expect_value(t, backf.important, f.important) + testing.expect_value(t, backf.no, nil) + testing.expect_value(t, backf.nos, nil) + testing.expect_value(t, backf.yes, f.yes) + testing.expect_value(t, backf.biggie, f.biggie) + testing.expect_value(t, backf.smallie, f.smallie) + testing.expect_value(t, backf.onetwenty, f.onetwenty) + testing.expect_value(t, backf.small_onetwenty, f.small_onetwenty) + testing.expect_value(t, backf.ignore_this, nil) s_equals, s_err := big.equals(&backf.smallest, &f.smallest) - ev(t, s_err, nil) + testing.expect_value(t, s_err, nil) if !s_equals { - errorf(t, "smallest: %v does not equal %v", big.itoa(&backf.smallest), big.itoa(&f.smallest)) + testing.expectf(t, false, "smallest: %v does not equal %v", big.itoa(&backf.smallest), big.itoa(&f.smallest)) } b_equals, b_err := big.equals(&backf.biggest, &f.biggest) - ev(t, b_err, nil) + testing.expect_value(t, b_err, nil) if !b_equals { - errorf(t, "biggest: %v does not equal %v", big.itoa(&backf.biggest), big.itoa(&f.biggest)) + testing.expectf(t, false, "biggest: %v does not equal %v", big.itoa(&backf.biggest), big.itoa(&f.biggest)) } } - - for _, leak in tracker.allocation_map { - errorf(t, "%v leaked %m\n", leak.location, leak.size) - } - - for bad_free in tracker.bad_free_array { - errorf(t, "%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) - } } @(test) test_marshalling_maybe :: proc(t: ^testing.T) { maybe_test: Maybe(int) = 1 data, err := cbor.marshal(maybe_test) - expect_value(t, err, nil) + defer delete(data) + testing.expect_value(t, err, nil) val, derr := cbor.decode(string(data)) - expect_value(t, derr, nil) + testing.expect_value(t, derr, nil) - expect_value(t, cbor.to_diagnostic_format(val), "1") + diag := cbor.to_diagnostic_format(val) + testing.expect_value(t, diag, "1") + delete(diag) maybe_dest: Maybe(int) uerr := cbor.unmarshal(string(data), &maybe_dest) - expect_value(t, uerr, nil) - expect_value(t, maybe_dest, 1) + testing.expect_value(t, uerr, nil) + testing.expect_value(t, maybe_dest, 1) } @(test) test_marshalling_nil_maybe :: proc(t: ^testing.T) { maybe_test: Maybe(int) data, err := cbor.marshal(maybe_test) - expect_value(t, err, nil) + defer delete(data) + testing.expect_value(t, err, nil) val, derr := cbor.decode(string(data)) - expect_value(t, derr, nil) + testing.expect_value(t, derr, nil) - expect_value(t, cbor.to_diagnostic_format(val), "nil") + diag := cbor.to_diagnostic_format(val) + testing.expect_value(t, diag, "nil") + delete(diag) maybe_dest: Maybe(int) uerr := cbor.unmarshal(string(data), &maybe_dest) - expect_value(t, uerr, nil) - expect_value(t, maybe_dest, nil) + testing.expect_value(t, uerr, nil) + testing.expect_value(t, maybe_dest, nil) } @(test) @@ -427,17 +327,24 @@ test_marshalling_union :: proc(t: ^testing.T) { { test: My_Union = My_Distinct("Hello, World!") data, err := cbor.marshal(test) - expect_value(t, err, nil) + defer delete(data) + testing.expect_value(t, err, nil) val, derr := cbor.decode(string(data)) - expect_value(t, derr, nil) + defer cbor.destroy(val) + testing.expect_value(t, derr, nil) - expect_value(t, cbor.to_diagnostic_format(val, -1), `1010(["My_Distinct", "Hello, World!"])`) + diag := cbor.to_diagnostic_format(val, -1) + defer delete(diag) + testing.expect_value(t, diag, `1010(["My_Distinct", "Hello, World!"])`) dest: My_Union uerr := cbor.unmarshal(string(data), &dest) - expect_value(t, uerr, nil) - expect_value(t, dest, My_Distinct("Hello, World!")) + testing.expect_value(t, uerr, nil) + testing.expect_value(t, dest, My_Distinct("Hello, World!")) + if str, ok := dest.(My_Distinct); ok { + delete(string(str)) + } } My_Union_No_Nil :: union #no_nil { @@ -450,17 +357,21 @@ test_marshalling_union :: proc(t: ^testing.T) { { test: My_Union_No_Nil = My_Struct{.Two} data, err := cbor.marshal(test) - expect_value(t, err, nil) + defer delete(data) + testing.expect_value(t, err, nil) val, derr := cbor.decode(string(data)) - expect_value(t, derr, nil) + defer cbor.destroy(val) + testing.expect_value(t, derr, nil) - expect_value(t, cbor.to_diagnostic_format(val, -1), `1010(["My_Struct", {"my_enum": 1}])`) + diag := cbor.to_diagnostic_format(val, -1) + defer delete(diag) + testing.expect_value(t, diag, `1010(["My_Struct", {"my_enum": 1}])`) dest: My_Union_No_Nil uerr := cbor.unmarshal(string(data), &dest) - expect_value(t, uerr, nil) - expect_value(t, dest, My_Struct{.Two}) + testing.expect_value(t, uerr, nil) + testing.expect_value(t, dest, My_Struct{.Two}) } } @@ -469,7 +380,7 @@ test_lying_length_array :: proc(t: ^testing.T) { // Input says this is an array of length max(u64), this should not allocate that amount. input := []byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42} _, err := cbor.decode(string(input)) - expect_value(t, err, io.Error.Unexpected_EOF) // .Out_Of_Memory would be bad. + testing.expect_value(t, err, io.Error.Unexpected_EOF) // .Out_Of_Memory would be bad. } @(test) @@ -691,65 +602,73 @@ test_encode_lists :: proc(t: ^testing.T) { expect_streamed_encoding(t, "\x9f\xff", &cbor.Array{}) { - bytes.buffer_reset(&buf) + buf: bytes.Buffer + bytes.buffer_init_allocator(&buf, 0, 0) + defer bytes.buffer_destroy(&buf) + stream := bytes.buffer_to_stream(&buf) + encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} err: cbor.Encode_Error err = cbor.encode_stream_begin(stream, .Array) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) { err = cbor.encode_stream_array_item(encoder, u8(1)) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) err = cbor.encode_stream_array_item(encoder, &cbor.Array{u8(2), u8(3)}) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) err = cbor.encode_stream_begin(stream, .Array) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) { err = cbor.encode_stream_array_item(encoder, u8(4)) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) err = cbor.encode_stream_array_item(encoder, u8(5)) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) } err = cbor.encode_stream_end(stream) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) } err = cbor.encode_stream_end(stream) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) - expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff"))) + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff"))) } { - bytes.buffer_reset(&buf) + buf: bytes.Buffer + bytes.buffer_init_allocator(&buf, 0, 0) + defer bytes.buffer_destroy(&buf) + stream := bytes.buffer_to_stream(&buf) + encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} err: cbor.Encode_Error err = cbor._encode_u8(stream, 2, .Array) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) a := "a" err = cbor.encode(encoder, &a) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) { err = cbor.encode_stream_begin(stream, .Map) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) b := "b" c := "c" err = cbor.encode_stream_map_entry(encoder, &b, &c) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) err = cbor.encode_stream_end(stream) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) } - expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x82\x61\x61\xbf\x61\x62\x61\x63\xff"))) + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x82\x61\x61\xbf\x61\x62\x61\x63\xff"))) } } @@ -807,30 +726,30 @@ expect_decoding :: proc(t: ^testing.T, encoded: string, decoded: string, type: t res, err := cbor.decode(encoded) defer cbor.destroy(res) - expect_value(t, reflect.union_variant_typeid(res), type, loc) - expect_value(t, err, nil, loc) + testing.expect_value(t, reflect.union_variant_typeid(res), type, loc) + testing.expect_value(t, err, nil, loc) str := cbor.to_diagnostic_format(res, padding=-1) defer delete(str) - 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) { res, err := cbor.decode(encoded) defer cbor.destroy(res) - expect_value(t, err, nil, loc) + testing.expect_value(t, err, nil, loc) if tag, is_tag := res.(^cbor.Tag); is_tag { - expect_value(t, tag.number, nr, loc) + testing.expect_value(t, tag.number, nr, loc) str := cbor.to_diagnostic_format(tag, padding=-1) defer delete(str) - expect_value(t, str, value_decoded, loc) + testing.expect_value(t, str, value_decoded, loc) } else { - errorf(t, "Value %#v is not a tag", res, loc) + testing.expectf(t, false, "Value %#v is not a tag", res, loc) } } @@ -838,35 +757,39 @@ expect_float :: proc(t: ^testing.T, encoded: string, expected: $T, loc := #calle res, err := cbor.decode(encoded) defer cbor.destroy(res) - expect_value(t, reflect.union_variant_typeid(res), typeid_of(T), loc) - expect_value(t, err, nil, loc) + testing.expect_value(t, reflect.union_variant_typeid(res), typeid_of(T), loc) + testing.expect_value(t, err, nil, loc) #partial switch r in res { case f16: - when T == f16 { expect_value(t, res, expected, loc) } else { unreachable() } + when T == f16 { testing.expect_value(t, res, expected, loc) } else { unreachable() } case f32: - when T == f32 { expect_value(t, res, expected, loc) } else { unreachable() } + when T == f32 { testing.expect_value(t, res, expected, loc) } else { unreachable() } case f64: - when T == f64 { expect_value(t, res, expected, loc) } else { unreachable() } + when T == f64 { testing.expect_value(t, res, expected, loc) } else { unreachable() } case: unreachable() } } -buf: bytes.Buffer -stream := bytes.buffer_to_stream(&buf) -encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} - expect_encoding :: proc(t: ^testing.T, val: cbor.Value, encoded: string, loc := #caller_location) { - bytes.buffer_reset(&buf) + buf: bytes.Buffer + bytes.buffer_init_allocator(&buf, 0, 0) + defer bytes.buffer_destroy(&buf) + stream := bytes.buffer_to_stream(&buf) + encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} - err := cbor.encode(encoder, val) - expect_value(t, err, nil, loc) - expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) + err := cbor.encode(encoder, val, loc) + testing.expect_value(t, err, nil, loc) + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) } expect_streamed_encoding :: proc(t: ^testing.T, encoded: string, values: ..cbor.Value, loc := #caller_location) { - bytes.buffer_reset(&buf) + buf: bytes.Buffer + bytes.buffer_init_allocator(&buf, 0, 0) + defer bytes.buffer_destroy(&buf) + stream := bytes.buffer_to_stream(&buf) + encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} for value, i in values { err: cbor.Encode_Error @@ -891,15 +814,15 @@ expect_streamed_encoding :: proc(t: ^testing.T, encoded: string, values: ..cbor. if err2 != nil { break } } case: - errorf(t, "%v does not support streamed encoding", reflect.union_variant_typeid(value)) + testing.expectf(t, false, "%v does not support streamed encoding", reflect.union_variant_typeid(value)) } - expect_value(t, err, nil, loc) - expect_value(t, err2, nil, loc) + testing.expect_value(t, err, nil, loc) + testing.expect_value(t, err2, nil, loc) } err := cbor.encode_stream_end(stream) - expect_value(t, err, nil, loc) + testing.expect_value(t, err, nil, loc) - expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) } diff --git a/tests/core/encoding/hex/test_core_hex.odin b/tests/core/encoding/hex/test_core_hex.odin index d928cd28e..6a00c9705 100644 --- a/tests/core/encoding/hex/test_core_hex.odin +++ b/tests/core/encoding/hex/test_core_hex.odin @@ -2,42 +2,6 @@ package test_core_hex import "core:encoding/hex" import "core:testing" -import "core:fmt" -import "core:os" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - hex_encode(&t) - hex_decode(&t) - hex_decode_sequence(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} CASES :: [][2]string{ {"11", "3131"}, @@ -49,10 +13,14 @@ CASES :: [][2]string{ hex_encode :: proc(t: ^testing.T) { for test in CASES { encoded := string(hex.encode(transmute([]byte)test[0])) - expect( + defer delete(encoded) + testing.expectf( t, encoded == test[1], - fmt.tprintf("encode: %q -> %q (should be: %q)", test[0], encoded, test[1]), + "encode: %q -> %q (should be: %q)", + test[0], + encoded, + test[1], ) } } @@ -61,11 +29,20 @@ hex_encode :: proc(t: ^testing.T) { hex_decode :: proc(t: ^testing.T) { for test in CASES { decoded, ok := hex.decode(transmute([]byte)test[1]) - expect(t, ok, fmt.tprintf("decode: %q not ok", test[1])) - expect( + defer delete(decoded) + testing.expectf( + t, + ok, + "decode: %q not ok", + test[1], + ) + testing.expectf( t, string(decoded) == test[0], - fmt.tprintf("decode: %q -> %q (should be: %q)", test[1], string(decoded), test[0]), + "decode: %q -> %q (should be: %q)", + test[1], + string(decoded), + test[0], ) } } @@ -73,20 +50,37 @@ hex_decode :: proc(t: ^testing.T) { @(test) hex_decode_sequence :: proc(t: ^testing.T) { b, ok := hex.decode_sequence("0x23") - expect(t, ok, "decode_sequence: 0x23 not ok") - expect(t, b == '#', fmt.tprintf("decode_sequence: 0x23 -> %c (should be: %c)", b, '#')) + testing.expect(t, ok, "decode_sequence: 0x23 not ok") + testing.expectf( + t, + b == '#', + "decode_sequence: 0x23 -> %c (should be: %c)", + b, + '#', + ) b, ok = hex.decode_sequence("0X3F") - expect(t, ok, "decode_sequence: 0X3F not ok") - expect(t, b == '?', fmt.tprintf("decode_sequence: 0X3F -> %c (should be: %c)", b, '?')) + testing.expect(t, ok, "decode_sequence: 0X3F not ok") + testing.expectf( + t, + b == '?', + "decode_sequence: 0X3F -> %c (should be: %c)", + b, + '?', + ) b, ok = hex.decode_sequence("2a") - expect(t, ok, "decode_sequence: 2a not ok") - expect(t, b == '*', fmt.tprintf("decode_sequence: 2a -> %c (should be: %c)", b, '*')) + testing.expect(t, ok, "decode_sequence: 2a not ok") + testing.expectf(t, + b == '*', + "decode_sequence: 2a -> %c (should be: %c)", + b, + '*', + ) _, ok = hex.decode_sequence("1") - expect(t, !ok, "decode_sequence: 1 should be too short") + testing.expect(t, !ok, "decode_sequence: 1 should be too short") _, ok = hex.decode_sequence("123") - expect(t, !ok, "decode_sequence: 123 should be too long") -} + testing.expect(t, !ok, "decode_sequence: 123 should be too long") +} \ No newline at end of file diff --git a/tests/core/encoding/hxa/test_core_hxa.odin b/tests/core/encoding/hxa/test_core_hxa.odin index 40c3c2e23..a8f3e94f6 100644 --- a/tests/core/encoding/hxa/test_core_hxa.odin +++ b/tests/core/encoding/hxa/test_core_hxa.odin @@ -6,127 +6,99 @@ package test_core_hxa import "core:encoding/hxa" import "core:fmt" import "core:testing" -import tc "tests:common" -TEAPOT_PATH :: "core/assets/HXA/teapot.hxa" +TEAPOT_PATH :: ODIN_ROOT + "tests/core/assets/HXA/teapot.hxa" -main :: proc() { - t := testing.T{} - - test_read(&t) - test_write(&t) - - tc.report(&t) -} +import "core:os" @test test_read :: proc(t: ^testing.T) { - filename := tc.get_data_path(t, TEAPOT_PATH) - defer delete(filename) + data, _ := os.read_entire_file(TEAPOT_PATH) + // file, err := hxa.read_from_file(TEAPOT_PATH) + file, err := hxa.read(data) + file.backing = data + file.allocator = context.allocator + hxa.file_destroy(file) + // fmt.printfln("%#v", file) - file, err := hxa.read_from_file(filename) e :: hxa.Read_Error.None - tc.expect(t, err == e, fmt.tprintf("%v: read_from_file(%v) -> %v != %v", #procedure, filename, err, e)) - defer hxa.file_destroy(file) + testing.expectf(t, err == e, "read_from_file(%v) -> %v != %v", TEAPOT_PATH, err, e) /* Header */ - tc.expect(t, file.magic_number == 0x417848, fmt.tprintf("%v: file.magic_number %v != %v", - #procedure, file.magic_number, 0x417848)) - tc.expect(t, file.version == 1, fmt.tprintf("%v: file.version %v != %v", - #procedure, file.version, 1)) - tc.expect(t, file.internal_node_count == 1, fmt.tprintf("%v: file.internal_node_count %v != %v", - #procedure, file.internal_node_count, 1)) + testing.expectf(t, file.magic_number == 0x417848, "file.magic_number %v != %v", file.magic_number, 0x417848) + testing.expectf(t, file.version == 1, "file.version %v != %v", file.version, 1) + testing.expectf(t, file.internal_node_count == 1, "file.internal_node_count %v != %v", file.internal_node_count, 1) /* Nodes (only one) */ - tc.expect(t, len(file.nodes) == 1, fmt.tprintf("%v: len(file.nodes) %v != %v", #procedure, len(file.nodes), 1)) + testing.expectf(t, len(file.nodes) == 1, "len(file.nodes) %v != %v", len(file.nodes), 1) m := &file.nodes[0].meta_data - tc.expect(t, len(m^) == 38, fmt.tprintf("%v: len(m^) %v != %v", #procedure, len(m^), 38)) + testing.expectf(t, len(m^) == 38, "len(m^) %v != %v", len(m^), 38) { e :: "Texture resolution" - tc.expect(t, m[0].name == e, fmt.tprintf("%v: m[0].name %v != %v", #procedure, m[0].name, e)) + testing.expectf(t, m[0].name == e, "m[0].name %v != %v", m[0].name, e) m_v, m_v_ok := m[0].value.([]i64le) - tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true)) - tc.expect(t, len(m_v) == 1, fmt.tprintf("%v: len(m_v) %v != %v", #procedure, len(m_v), 1)) - tc.expect(t, m_v[0] == 1024, fmt.tprintf("%v: m_v[0] %v != %v", #procedure, len(m_v), 1024)) + testing.expectf(t, m_v_ok, "m_v_ok %v != %v", m_v_ok, true) + testing.expectf(t, len(m_v) == 1, "len(m_v) %v != %v", len(m_v), 1) + testing.expectf(t, m_v[0] == 1024, "m_v[0] %v != %v", len(m_v), 1024) } { e :: "Validate" - tc.expect(t, m[37].name == e, fmt.tprintf("%v: m[37].name %v != %v", #procedure, m[37].name, e)) + testing.expectf(t, m[37].name == e, "m[37].name %v != %v", m[37].name, e) m_v, m_v_ok := m[37].value.([]i64le) - tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true)) - tc.expect(t, len(m_v) == 1, fmt.tprintf("%v: len(m_v) %v != %v", #procedure, len(m_v), 1)) - tc.expect(t, m_v[0] == -2054847231, fmt.tprintf("%v: m_v[0] %v != %v", #procedure, len(m_v), -2054847231)) + testing.expectf(t, m_v_ok, "m_v_ok %v != %v", m_v_ok, true) + testing.expectf(t, len(m_v) == 1, "len(m_v) %v != %v", len(m_v), 1) + testing.expectf(t, m_v[0] == -2054847231, "m_v[0] %v != %v", len(m_v), -2054847231) } /* Node content */ v, v_ok := file.nodes[0].content.(hxa.Node_Geometry) - tc.expect(t, v_ok, fmt.tprintf("%v: v_ok %v != %v", #procedure, v_ok, true)) + testing.expectf(t, v_ok, "v_ok %v != %v", v_ok, true) - tc.expect(t, v.vertex_count == 530, fmt.tprintf("%v: v.vertex_count %v != %v", #procedure, v.vertex_count, 530)) - tc.expect(t, v.edge_corner_count == 2026, fmt.tprintf("%v: v.edge_corner_count %v != %v", - #procedure, v.edge_corner_count, 2026)) - tc.expect(t, v.face_count == 517, fmt.tprintf("%v: v.face_count %v != %v", #procedure, v.face_count, 517)) + testing.expectf(t, v.vertex_count == 530, "v.vertex_count %v != %v", v.vertex_count, 530) + testing.expectf(t, v.edge_corner_count == 2026, "v.edge_corner_count %v != %v", v.edge_corner_count, 2026) + testing.expectf(t, v.face_count == 517, "v.face_count %v != %v", v.face_count, 517) /* Vertex stack */ - tc.expect(t, len(v.vertex_stack) == 1, fmt.tprintf("%v: len(v.vertex_stack) %v != %v", - #procedure, len(v.vertex_stack), 1)) + testing.expectf(t, len(v.vertex_stack) == 1, "len(v.vertex_stack) %v != %v", len(v.vertex_stack), 1) { e := "vertex" - tc.expect(t, v.vertex_stack[0].name == e, fmt.tprintf("%v: v.vertex_stack[0].name %v != %v", - #procedure, v.vertex_stack[0].name, e)) + testing.expectf(t, v.vertex_stack[0].name == e, "v.vertex_stack[0].name %v != %v", v.vertex_stack[0].name, e) } - tc.expect(t, v.vertex_stack[0].components == 3, fmt.tprintf("%v: v.vertex_stack[0].components %v != %v", - #procedure, v.vertex_stack[0].components, 3)) + testing.expectf(t, v.vertex_stack[0].components == 3, "v.vertex_stack[0].components %v != %v", v.vertex_stack[0].components, 3) /* Vertex stack data */ vs_d, vs_d_ok := v.vertex_stack[0].data.([]f64le) - tc.expect(t, vs_d_ok, fmt.tprintf("%v: vs_d_ok %v != %v", #procedure, vs_d_ok, true)) - tc.expect(t, len(vs_d) == 1590, fmt.tprintf("%v: len(vs_d) %v != %v", #procedure, len(vs_d), 1590)) - - tc.expect(t, vs_d[0] == 4.06266, fmt.tprintf("%v: vs_d[0] %v (%h) != %v (%h)", - #procedure, vs_d[0], vs_d[0], 4.06266, 4.06266)) - tc.expect(t, vs_d[1] == 2.83457, fmt.tprintf("%v: vs_d[1] %v (%h) != %v (%h)", - #procedure, vs_d[1], vs_d[1], 2.83457, 2.83457)) - tc.expect(t, vs_d[2] == 0hbfbc5da6a4441787, fmt.tprintf("%v: vs_d[2] %v (%h) != %v (%h)", - #procedure, vs_d[2], vs_d[2], - 0hbfbc5da6a4441787, 0hbfbc5da6a4441787)) - tc.expect(t, vs_d[3] == 0h4010074fb549f948, fmt.tprintf("%v: vs_d[3] %v (%h) != %v (%h)", - #procedure, vs_d[3], vs_d[3], - 0h4010074fb549f948, 0h4010074fb549f948)) - tc.expect(t, vs_d[1587] == 0h400befa82e87d2c7, fmt.tprintf("%v: vs_d[1587] %v (%h) != %v (%h)", - #procedure, vs_d[1587], vs_d[1587], - 0h400befa82e87d2c7, 0h400befa82e87d2c7)) - tc.expect(t, vs_d[1588] == 2.83457, fmt.tprintf("%v: vs_d[1588] %v (%h) != %v (%h)", - #procedure, vs_d[1588], vs_d[1588], 2.83457, 2.83457)) - tc.expect(t, vs_d[1589] == -1.56121, fmt.tprintf("%v: vs_d[1589] %v (%h) != %v (%h)", - #procedure, vs_d[1589], vs_d[1589], -1.56121, -1.56121)) + testing.expectf(t, vs_d_ok, "vs_d_ok %v != %v", vs_d_ok, true) + testing.expectf(t, len(vs_d) == 1590, "len(vs_d) %v != %v", len(vs_d), 1590) + testing.expectf(t, vs_d[0] == 4.06266, "vs_d[0] %v (%h) != %v (%h)", vs_d[0], vs_d[0], 4.06266, 4.06266) + testing.expectf(t, vs_d[1] == 2.83457, "vs_d[1] %v (%h) != %v (%h)", vs_d[1], vs_d[1], 2.83457, 2.83457) + testing.expectf(t, vs_d[2] == 0hbfbc5da6a4441787, "vs_d[2] %v (%h) != %v (%h)", vs_d[2], vs_d[2], 0hbfbc5da6a4441787, 0hbfbc5da6a4441787) + testing.expectf(t, vs_d[3] == 0h4010074fb549f948, "vs_d[3] %v (%h) != %v (%h)", vs_d[3], vs_d[3], 0h4010074fb549f948, 0h4010074fb549f948) + testing.expectf(t, vs_d[1587] == 0h400befa82e87d2c7, "vs_d[1587] %v (%h) != %v (%h)", vs_d[1587], vs_d[1587], 0h400befa82e87d2c7, 0h400befa82e87d2c7) + testing.expectf(t, vs_d[1588] == 2.83457, "vs_d[1588] %v (%h) != %v (%h)", vs_d[1588], vs_d[1588], 2.83457, 2.83457) + testing.expectf(t, vs_d[1589] == -1.56121, "vs_d[1589] %v (%h) != %v (%h)", vs_d[1589], vs_d[1589], -1.56121, -1.56121) /* Corner stack */ - tc.expect(t, len(v.corner_stack) == 1, - fmt.tprintf("%v: len(v.corner_stack) %v != %v", #procedure, len(v.corner_stack), 1)) + testing.expectf(t, len(v.corner_stack) == 1, "len(v.corner_stack) %v != %v", len(v.corner_stack), 1) { e := "reference" - tc.expect(t, v.corner_stack[0].name == e, fmt.tprintf("%v: v.corner_stack[0].name %v != %v", - #procedure, v.corner_stack[0].name, e)) + testing.expectf(t, v.corner_stack[0].name == e, "v.corner_stack[0].name %v != %v", v.corner_stack[0].name, e) } - tc.expect(t, v.corner_stack[0].components == 1, fmt.tprintf("%v: v.corner_stack[0].components %v != %v", - #procedure, v.corner_stack[0].components, 1)) + testing.expectf(t, v.corner_stack[0].components == 1, "v.corner_stack[0].components %v != %v", v.corner_stack[0].components, 1) /* Corner stack data */ cs_d, cs_d_ok := v.corner_stack[0].data.([]i32le) - tc.expect(t, cs_d_ok, fmt.tprintf("%v: cs_d_ok %v != %v", #procedure, cs_d_ok, true)) - tc.expect(t, len(cs_d) == 2026, fmt.tprintf("%v: len(cs_d) %v != %v", #procedure, len(cs_d), 2026)) - tc.expect(t, cs_d[0] == 6, fmt.tprintf("%v: cs_d[0] %v != %v", #procedure, cs_d[0], 6)) - tc.expect(t, cs_d[2025] == -32, fmt.tprintf("%v: cs_d[2025] %v != %v", #procedure, cs_d[2025], -32)) + testing.expectf(t, cs_d_ok, "cs_d_ok %v != %v", cs_d_ok, true) + testing.expectf(t, len(cs_d) == 2026, "len(cs_d) %v != %v", len(cs_d), 2026) + testing.expectf(t, cs_d[0] == 6, "cs_d[0] %v != %v", cs_d[0], 6) + testing.expectf(t, cs_d[2025] == -32, "cs_d[2025] %v != %v", cs_d[2025], -32) /* Edge and face stacks (empty) */ - tc.expect(t, len(v.edge_stack) == 0, fmt.tprintf("%v: len(v.edge_stack) %v != %v", - #procedure, len(v.edge_stack), 0)) - tc.expect(t, len(v.face_stack) == 0, fmt.tprintf("%v: len(v.face_stack) %v != %v", - #procedure, len(v.face_stack), 0)) + testing.expectf(t, len(v.edge_stack) == 0, "len(v.edge_stack) %v != %v", len(v.edge_stack), 0) + testing.expectf(t, len(v.face_stack) == 0, "len(v.face_stack) %v != %v", len(v.face_stack), 0) } @test @@ -154,72 +126,64 @@ test_write :: proc(t: ^testing.T) { n, write_err := hxa.write(buf, w_file) write_e :: hxa.Write_Error.None - tc.expect(t, write_err == write_e, fmt.tprintf("%v: write_err %v != %v", #procedure, write_err, write_e)) - tc.expect(t, n == required_size, fmt.tprintf("%v: n %v != %v", #procedure, n, required_size)) + testing.expectf(t, write_err == write_e, fmt.tprintf("write_err %v != %v", write_err, write_e)) + testing.expectf(t, n == required_size, fmt.tprintf("n %v != %v", n, required_size)) file, read_err := hxa.read(buf) read_e :: hxa.Read_Error.None - tc.expect(t, read_err == read_e, fmt.tprintf("%v: read_err %v != %v", #procedure, read_err, read_e)) + testing.expectf(t, read_err == read_e, fmt.tprintf("read_err %v != %v", read_err, read_e)) defer hxa.file_destroy(file) - tc.expect(t, file.magic_number == 0x417848, fmt.tprintf("%v: file.magic_number %v != %v", - #procedure, file.magic_number, 0x417848)) - tc.expect(t, file.version == 3, fmt.tprintf("%v: file.version %v != %v", #procedure, file.version, 3)) - tc.expect(t, file.internal_node_count == 1, fmt.tprintf("%v: file.internal_node_count %v != %v", - #procedure, file.internal_node_count, 1)) + testing.expectf(t, file.magic_number == 0x417848, fmt.tprintf("file.magic_number %v != %v", file.magic_number, 0x417848)) + testing.expectf(t, file.version == 3, fmt.tprintf("file.version %v != %v", file.version, 3)) + testing.expectf(t, file.internal_node_count == 1, fmt.tprintf("file.internal_node_count %v != %v", file.internal_node_count, 1)) - tc.expect(t, len(file.nodes) == len(w_file.nodes), fmt.tprintf("%v: len(file.nodes) %v != %v", - #procedure, len(file.nodes), len(w_file.nodes))) + testing.expectf(t, len(file.nodes) == len(w_file.nodes), fmt.tprintf("len(file.nodes) %v != %v", len(file.nodes), len(w_file.nodes))) m := &file.nodes[0].meta_data w_m := &w_file.nodes[0].meta_data - tc.expect(t, len(m^) == len(w_m^), fmt.tprintf("%v: len(m^) %v != %v", #procedure, len(m^), len(w_m^))) - tc.expect(t, m[0].name == w_m[0].name, fmt.tprintf("%v: m[0].name %v != %v", #procedure, m[0].name, w_m[0].name)) + testing.expectf(t, len(m^) == len(w_m^), fmt.tprintf("len(m^) %v != %v", len(m^), len(w_m^))) + testing.expectf(t, m[0].name == w_m[0].name, fmt.tprintf("m[0].name %v != %v", m[0].name, w_m[0].name)) m_v, m_v_ok := m[0].value.([]f64le) - tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true)) - tc.expect(t, len(m_v) == len(n1_m1_value), fmt.tprintf("%v: %v != len(m_v) %v", - #procedure, len(m_v), len(n1_m1_value))) + testing.expectf(t, m_v_ok, fmt.tprintf("m_v_ok %v != %v", m_v_ok, true)) + testing.expectf(t, len(m_v) == len(n1_m1_value), fmt.tprintf("%v != len(m_v) %v", len(m_v), len(n1_m1_value))) for i := 0; i < len(m_v); i += 1 { - tc.expect(t, m_v[i] == n1_m1_value[i], fmt.tprintf("%v: m_v[%d] %v != %v", - #procedure, i, m_v[i], n1_m1_value[i])) + testing.expectf(t, m_v[i] == n1_m1_value[i], fmt.tprintf("m_v[%d] %v != %v", i, m_v[i], n1_m1_value[i])) } v, v_ok := file.nodes[0].content.(hxa.Node_Image) - tc.expect(t, v_ok, fmt.tprintf("%v: v_ok %v != %v", #procedure, v_ok, true)) - tc.expect(t, v.type == n1_content.type, fmt.tprintf("%v: v.type %v != %v", #procedure, v.type, n1_content.type)) - tc.expect(t, len(v.resolution) == 3, fmt.tprintf("%v: len(v.resolution) %v != %v", - #procedure, len(v.resolution), 3)) - tc.expect(t, len(v.image_stack) == len(n1_content.image_stack), fmt.tprintf("%v: len(v.image_stack) %v != %v", - #procedure, len(v.image_stack), len(n1_content.image_stack))) + testing.expectf(t, v_ok, fmt.tprintf("v_ok %v != %v", v_ok, true)) + testing.expectf(t, v.type == n1_content.type, fmt.tprintf("v.type %v != %v", v.type, n1_content.type)) + testing.expectf(t, len(v.resolution) == 3, fmt.tprintf("len(v.resolution) %v != %v", len(v.resolution), 3)) + testing.expectf(t, len(v.image_stack) == len(n1_content.image_stack), fmt.tprintf("len(v.image_stack) %v != %v", len(v.image_stack), len(n1_content.image_stack))) for i := 0; i < len(v.image_stack); i += 1 { - tc.expect(t, v.image_stack[i].name == n1_content.image_stack[i].name, - fmt.tprintf("%v: v.image_stack[%d].name %v != %v", - #procedure, i, v.image_stack[i].name, n1_content.image_stack[i].name)) - tc.expect(t, v.image_stack[i].components == n1_content.image_stack[i].components, - fmt.tprintf("%v: v.image_stack[%d].components %v != %v", - #procedure, i, v.image_stack[i].components, n1_content.image_stack[i].components)) + testing.expectf(t, v.image_stack[i].name == n1_content.image_stack[i].name, + fmt.tprintf("v.image_stack[%d].name %v != %v", + i, v.image_stack[i].name, n1_content.image_stack[i].name)) + testing.expectf(t, v.image_stack[i].components == n1_content.image_stack[i].components, + fmt.tprintf("v.image_stack[%d].components %v != %v", + i, v.image_stack[i].components, n1_content.image_stack[i].components)) switch n1_t in n1_content.image_stack[i].data { case []u8: - tc.expect(t, false, fmt.tprintf("%v: n1_content.image_stack[i].data []u8", #procedure)) + testing.expectf(t, false, fmt.tprintf("n1_content.image_stack[i].data []u8", #procedure)) case []i32le: - tc.expect(t, false, fmt.tprintf("%v: n1_content.image_stack[i].data []i32le", #procedure)) + testing.expectf(t, false, fmt.tprintf("n1_content.image_stack[i].data []i32le", #procedure)) case []f32le: l, l_ok := v.image_stack[i].data.([]f32le) - tc.expect(t, l_ok, fmt.tprintf("%v: l_ok %v != %v", #procedure, l_ok, true)) - tc.expect(t, len(l) == len(n1_t), fmt.tprintf("%v: len(l) %v != %v", #procedure, len(l), len(n1_t))) + testing.expectf(t, l_ok, fmt.tprintf("l_ok %v != %v", l_ok, true)) + testing.expectf(t, len(l) == len(n1_t), fmt.tprintf("len(l) %v != %v", len(l), len(n1_t))) for j := 0; j < len(l); j += 1 { - tc.expect(t, l[j] == n1_t[j], fmt.tprintf("%v: l[%d] %v (%h) != %v (%h)", - #procedure, j, l[j], l[j], n1_t[j], n1_t[j])) + testing.expectf(t, l[j] == n1_t[j], fmt.tprintf("l[%d] %v (%h) != %v (%h)", j, l[j], l[j], n1_t[j], n1_t[j])) } case []f64le: l, l_ok := v.image_stack[i].data.([]f64le) - tc.expect(t, l_ok, fmt.tprintf("%v: l_ok %v != %v", #procedure, l_ok, true)) - tc.expect(t, len(l) == len(n1_t), fmt.tprintf("%v: len(l) %v != %v", #procedure, len(l), len(n1_t))) + testing.expectf(t, l_ok, fmt.tprintf("l_ok %v != %v", l_ok, true)) + testing.expectf(t, len(l) == len(n1_t), fmt.tprintf("len(l) %v != %v", len(l), len(n1_t))) for j := 0; j < len(l); j += 1 { - tc.expect(t, l[j] == n1_t[j], fmt.tprintf("%v: l[%d] %v != %v", #procedure, j, l[j], n1_t[j])) + testing.expectf(t, l[j] == n1_t[j], fmt.tprintf("l[%d] %v != %v", j, l[j], n1_t[j])) } } } -} +} \ No newline at end of file diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index 813d11b2c..10e09df3b 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -2,45 +2,8 @@ package test_core_json import "core:encoding/json" import "core:testing" -import "core:fmt" -import "core:os" import "core:mem/virtual" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - parse_json(&t) - marshal_json(&t) - unmarshal_json(&t) - surrogate(&t) - utf8_string_of_multibyte_characters(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} +import "base:runtime" @test parse_json :: proc(t: ^testing.T) { @@ -72,10 +35,9 @@ parse_json :: proc(t: ^testing.T) { } ` - _, err := json.parse(transmute([]u8)json_data) - - msg := fmt.tprintf("Expected `json.parse` to return nil, got %v", err) - expect(t, err == nil, msg) + val, err := json.parse(transmute([]u8)json_data) + json.destroy_value(val) + testing.expectf(t, err == nil, "Expected `json.parse` to return nil, got %v", err) } @test @@ -83,7 +45,7 @@ out_of_memory_in_parse_json :: proc(t: ^testing.T) { arena: virtual.Arena arena_buffer: [256]byte arena_init_error := virtual.arena_init_buffer(&arena, arena_buffer[:]) - testing.expect(t, arena_init_error == nil, fmt.tprintf("Expected arena initialization to not return error, got: %v\n", arena_init_error)) + testing.expectf(t, arena_init_error == nil, "Expected arena initialization to not return error, got: %v\n", arena_init_error) context.allocator = virtual.arena_allocator(&arena) @@ -114,11 +76,11 @@ out_of_memory_in_parse_json :: proc(t: ^testing.T) { } ` - _, err := json.parse(transmute([]u8)json_data) + val, err := json.parse(transmute([]u8)json_data) + json.destroy_value(val) expected_error := json.Error.Out_Of_Memory - msg := fmt.tprintf("Expected `json.parse` to fail with %v, got %v", expected_error, err) - expect(t, err == json.Error.Out_Of_Memory, msg) + testing.expectf(t, err == json.Error.Out_Of_Memory, "Expected `json.parse` to fail with %v, got %v", expected_error, err) } @test @@ -134,9 +96,9 @@ marshal_json :: proc(t: ^testing.T) { b = 5, } - _, err := json.marshal(my_struct) - msg := fmt.tprintf("Expected `json.marshal` to return nil, got %v", err) - expect(t, err == nil, msg) + data, err := json.marshal(my_struct) + defer delete(data) + testing.expectf(t, err == nil, "Expected `json.marshal` to return nil, got %v", err) } PRODUCTS := ` @@ -378,17 +340,12 @@ unmarshal_json :: proc(t: ^testing.T) { err := json.unmarshal(transmute([]u8)PRODUCTS, &g, json.DEFAULT_SPECIFICATION) defer cleanup(g) - msg := fmt.tprintf("Expected `json.unmarshal` to return nil, got %v", err) - expect(t, err == nil, msg) - - msg = fmt.tprintf("Expected %v products to have been unmarshaled, got %v", len(original_data.products), len(g.products)) - expect(t, len(g.products) == len(original_data.products), msg) - - msg = fmt.tprintf("Expected cash to have been unmarshaled as %v, got %v", original_data.cash, g.cash) - expect(t, original_data.cash == g.cash, msg) + testing.expectf(t, err == nil, "Expected `json.unmarshal` to return nil, got %v", err) + testing.expectf(t, len(g.products) == len(original_data.products), "Expected %v products to have been unmarshaled, got %v", len(original_data.products), len(g.products)) + testing.expectf(t, original_data.cash == g.cash, "Expected cash to have been unmarshaled as %v, got %v", original_data.cash, g.cash) for p, i in g.products { - expect(t, p == original_data.products[i], "Producted unmarshaled improperly") + testing.expect(t, p == original_data.products[i], "Producted unmarshaled improperly") } } @@ -397,17 +354,75 @@ surrogate :: proc(t: ^testing.T) { input := `+ + * 😃 - /` out, err := json.marshal(input) - expect(t, err == nil, fmt.tprintf("Expected `json.marshal(%q)` to return a nil error, got %v", input, err)) + defer delete(out) + testing.expectf(t, err == nil, "Expected `json.marshal(%q)` to return a nil error, got %v", input, err) back: string uerr := json.unmarshal(out, &back) - expect(t, uerr == nil, fmt.tprintf("Expected `json.unmarshal(%q)` to return a nil error, got %v", string(out), uerr)) - expect(t, back == input, fmt.tprintf("Expected `json.unmarshal(%q)` to return %q, got %v", string(out), input, uerr)) + defer delete(back) + testing.expectf(t, uerr == nil, "Expected `json.unmarshal(%q)` to return a nil error, got %v", string(out), uerr) + testing.expectf(t, back == input, "Expected `json.unmarshal(%q)` to return %q, got %v", string(out), input, uerr) } @test utf8_string_of_multibyte_characters :: proc(t: ^testing.T) { - _, err := json.parse_string(`"🐛✅"`) - msg := fmt.tprintf("Expected `json.parse` to return nil, got %v", err) - expect(t, err == nil, msg) + val, err := json.parse_string(`"🐛✅"`) + defer json.destroy_value(val) + testing.expectf(t, err == nil, "Expected `json.parse` to return nil, got %v", err) } + +@test +struct_with_ignore_tags :: proc(t: ^testing.T) { + My_Struct :: struct { + a: string `json:"-"`, + } + + my_struct := My_Struct{ + a = "test", + } + + my_struct_marshaled, marshal_err := json.marshal(my_struct) + defer delete(my_struct_marshaled) + + testing.expectf(t, marshal_err == nil, "Expected `json.marshal` to return nil error, got %v", marshal_err) + + my_struct_json := transmute(string)my_struct_marshaled + expected_json := `{}` + + testing.expectf(t, expected_json == my_struct_json, "Expected `json.marshal` to return %s, got %s", expected_json, my_struct_json) +} + +@test +map_with_integer_keys :: proc(t: ^testing.T) { + my_map := make(map[i32]string) + defer delete_map(my_map) + + my_map[-1] = "a" + my_map[0] = "b" + my_map[42] = "c" + my_map[99999999] = "d" + + marshaled_data, marshal_err := json.marshal(my_map) + defer delete(marshaled_data) + + testing.expectf(t, marshal_err == nil, "Expected `json.marshal` to return nil error, got %v", marshal_err) + + my_map2 := make(map[i32]string) + defer delete_map(my_map2) + + unmarshal_err := json.unmarshal(marshaled_data, &my_map2) + defer for key, item in my_map2 { + runtime.delete_string(item) + } + testing.expectf(t, unmarshal_err == nil, "Expected `json.unmarshal` to return nil, got %v", unmarshal_err) + + testing.expectf(t, len(my_map) == len(my_map2), "Expected %v map items to have been unmarshaled, got %v", len(my_map), len(my_map2)) + + for key, item in my_map { + testing.expectf(t, key in my_map2, "Expected key %v to be present in unmarshaled map", key) + + if key in my_map2 { + testing.expectf(t, runtime.string_eq(item, my_map2[key]), "Expected value %s to be present in unmarshaled map", key) + } + } +} \ No newline at end of file diff --git a/tests/core/encoding/uuid/test_core_uuid.odin b/tests/core/encoding/uuid/test_core_uuid.odin new file mode 100644 index 000000000..619534a7f --- /dev/null +++ b/tests/core/encoding/uuid/test_core_uuid.odin @@ -0,0 +1,439 @@ +package test_core_uuid + +import "core:crypto" +import "core:encoding/uuid" +import uuid_legacy "core:encoding/uuid/legacy" +import "core:log" +import "core:slice" +import "core:testing" +import "core:time" + +@(test) +test_version_and_variant :: proc(t: ^testing.T) { + context.random_generator = crypto.random_generator() + + v1 := uuid.generate_v1(0) + v3 := uuid_legacy.generate_v3(uuid.Namespace_DNS, "") + v4 := uuid.generate_v4() + v5 := uuid_legacy.generate_v5(uuid.Namespace_DNS, "") + v6 := uuid.generate_v6() + v7 := uuid.generate_v7() + + _v8_array: [16]u8 = 0xff + v8_int := uuid.stamp_v8(max(u128)) + v8_array := uuid.stamp_v8(_v8_array) + v8_slice := uuid.stamp_v8(_v8_array[:]) + + v8_hash := uuid.generate_v8_hash(uuid.Namespace_DNS, "", .SHA512) + + testing.expect_value(t, uuid.version(v1), 1) + testing.expect_value(t, uuid.variant(v1), uuid.Variant_Type.RFC_4122) + testing.expect_value(t, uuid.version(v3), 3) + testing.expect_value(t, uuid.variant(v3), uuid.Variant_Type.RFC_4122) + testing.expect_value(t, uuid.version(v4), 4) + testing.expect_value(t, uuid.variant(v4), uuid.Variant_Type.RFC_4122) + testing.expect_value(t, uuid.version(v5), 5) + testing.expect_value(t, uuid.variant(v5), uuid.Variant_Type.RFC_4122) + testing.expect_value(t, uuid.version(v6), 6) + testing.expect_value(t, uuid.variant(v6), uuid.Variant_Type.RFC_4122) + testing.expect_value(t, uuid.version(v7), 7) + testing.expect_value(t, uuid.variant(v7), uuid.Variant_Type.RFC_4122) + + testing.expect_value(t, uuid.version(v8_int), 8) + testing.expect_value(t, uuid.variant(v8_int), uuid.Variant_Type.RFC_4122) + testing.expect_value(t, uuid.version(v8_array), 8) + testing.expect_value(t, uuid.variant(v8_array), uuid.Variant_Type.RFC_4122) + testing.expect_value(t, uuid.version(v8_slice), 8) + testing.expect_value(t, uuid.variant(v8_slice), uuid.Variant_Type.RFC_4122) + + testing.expect_value(t, uuid.version(v8_hash), 8) + testing.expect_value(t, uuid.variant(v8_hash), uuid.Variant_Type.RFC_4122) +} + +@(test) +test_timestamps :: proc(t: ^testing.T) { + // This test makes sure that timestamps are recoverable and have not been + // overwritten by neighboring bits, taking into account precision loss. + context.random_generator = crypto.random_generator() + + N :: max(i64) + + max_time := time.Time { N } + + mac: [6]byte + v1 := uuid.generate_v1(0, mac, max_time) + v6 := uuid.generate_v6(0, mac, max_time) + v7 := uuid.generate_v7(max_time) + // The counter version keeps its time in the same place as the basic version, + // this is just for the sake of completeness. + v7_counter := uuid.generate_v7(0, max_time) + + v1_time := uuid.time_v1(v1) + v6_time := uuid.time_v6(v6) + v7_time := uuid.time_v7(v7) + v7_counter_time := uuid.time_v7(v7_counter) + + // I hope the compiler doesn't ever optimize this out. + max_time_hns_resolution := time.Time { N / 100 * 100 } + max_time_ms_resolution := time.Time { N / 1e6 * 1e6 } + + testing.expectf(t, + time.diff(max_time_hns_resolution, v1_time) == 0, + "v1 UUID timestamp is invalid, expected %x, got %x", + max_time_hns_resolution, v1_time) + + testing.expectf(t, + time.diff(max_time_hns_resolution, v6_time) == 0, + "v6 UUID timestamp is invalid, expected %x, got %x", + max_time_hns_resolution, v6_time) + + testing.expectf(t, + time.diff(max_time_ms_resolution, v7_time) == 0, + "v7 UUID timestamp is invalid, expected %x, got %x", + max_time_ms_resolution, v7_time) + + testing.expectf(t, + time.diff(max_time_ms_resolution, v7_counter_time) == 0, + "v7 UUID (with counter) timestamp is invalid, expected %x, got %x", + max_time_ms_resolution, v7_counter_time) +} + +@(test) +test_v8_hash_implementation :: proc(t: ^testing.T) { + // This example and its results are derived from RFC 9562. + // https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv8-value-n + + id := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.example.com", .SHA256) + id_str := uuid.to_string(id) + defer delete(id_str) + testing.expect_value(t, id_str, "5c146b14-3c52-8afd-938a-375d0df1fbf6") +} + +@(test) +test_legacy_namespaced_uuids :: proc(t: ^testing.T) { + TEST_NAME :: "0123456789ABCDEF0123456789ABCDEF" + + Expected_Result :: struct { + namespace: uuid.Identifier, + v3, v5: string, + } + + Expected_Results := [?]Expected_Result { + { uuid.Namespace_DNS, "80147f37-36db-3b82-b78f-810c3c6504ba", "18394c41-13a2-593f-abf2-a63e163c2860" }, + { uuid.Namespace_URL, "8136789b-8e16-3fbd-800b-1587e2f22521", "07337422-eb77-5fd3-99af-c7f59e641e13" }, + { uuid.Namespace_OID, "adbb95bc-ea50-3226-9a75-20c34a6030f8", "24db9b0f-70b8-53c4-a301-f695ce17276d" }, + { uuid.Namespace_X500, "a8965ad1-0e54-3d65-b933-8b7cca8e8313", "3012bf2d-fac4-5187-9825-493e6636b936" }, + } + + for exp in Expected_Results { + v3 := uuid_legacy.generate_v3(exp.namespace, TEST_NAME) + v5 := uuid_legacy.generate_v5(exp.namespace, TEST_NAME) + + v3_str := uuid.to_string(v3) + defer delete(v3_str) + + v5_str := uuid.to_string(v5) + defer delete(v5_str) + + testing.expect_value(t, v3_str, exp.v3) + testing.expect_value(t, v5_str, exp.v5) + } +} + +@(test) +test_v1 :: proc(t: ^testing.T) { + context.random_generator = crypto.random_generator() + + point_a := time.unix(1, 0) + point_b := time.unix(3, 0) + point_c := time.unix(5, 0) + + CLOCK :: 0x3A1A + mac := [6]u8{0xFF, 0x10, 0xAA, 0x55, 0x01, 0xFF} + + v1_a := uuid.generate_v1(CLOCK, mac, point_a) + v1_b := uuid.generate_v1(CLOCK, mac, point_b) + v1_c := uuid.generate_v1(CLOCK, mac, point_c) + + testing.expect_value(t, uuid.clock_seq(v1_a), CLOCK) + + extracted_mac := uuid.node(v1_a) + for i in 0 ..< len(mac) { + testing.expect(t, mac[i] == extracted_mac[i]) + } + + time_a := uuid.time_v1(v1_a) + time_b := uuid.time_v1(v1_b) + time_c := uuid.time_v1(v1_c) + + log.debugf("A: %02x, %v", v1_a, time_a) + log.debugf("B: %02x, %v", v1_b, time_b) + log.debugf("C: %02x, %v", v1_c, time_c) + + testing.expect(t, time.diff(time_a, time_b) > 0, "The time on the later-generated v1 UUID is earlier than its successor.") + testing.expect(t, time.diff(time_b, time_c) > 0, "The time on the later-generated v1 UUID is earlier than its successor.") + testing.expect(t, time.diff(time_a, time_c) > 0, "The time on the later-generated v1 UUID is earlier than its successor.") +} + +@(test) +test_v6 :: proc(t: ^testing.T) { + context.random_generator = crypto.random_generator() + + point_a := time.unix(1, 0) + point_b := time.unix(3, 0) + point_c := time.unix(5, 0) + + CLOCK :: 0x3A1A + mac := [6]u8{0xFF, 0x10, 0xAA, 0x55, 0x01, 0xFF} + + v6_a := uuid.generate_v6(CLOCK, mac, point_a) + v6_b := uuid.generate_v6(CLOCK, mac, point_b) + v6_c := uuid.generate_v6(CLOCK, mac, point_c) + + testing.expect_value(t, uuid.clock_seq(v6_a), CLOCK) + + extracted_mac := uuid.node(v6_a) + for i in 0 ..< len(mac) { + testing.expect(t, mac[i] == extracted_mac[i]) + } + + time_a := uuid.time_v6(v6_a) + time_b := uuid.time_v6(v6_b) + time_c := uuid.time_v6(v6_c) + + log.debugf("A: %02x, %v", v6_a, time_a) + log.debugf("B: %02x, %v", v6_b, time_b) + log.debugf("C: %02x, %v", v6_c, time_c) + + testing.expect(t, time.diff(time_a, time_b) > 0, "The time on the later-generated v6 UUID is earlier than its successor.") + testing.expect(t, time.diff(time_b, time_c) > 0, "The time on the later-generated v6 UUID is earlier than its successor.") + testing.expect(t, time.diff(time_a, time_c) > 0, "The time on the later-generated v6 UUID is earlier than its successor.") +} + +@(test) +test_v7 :: proc(t: ^testing.T) { + context.random_generator = crypto.random_generator() + + point_a := time.unix(1, 0) + point_b := time.unix(3, 0) + point_c := time.unix(5, 0) + + v7_a := uuid.generate_v7(point_a) + v7_b := uuid.generate_v7(point_b) + v7_c := uuid.generate_v7(point_c) + + time_a := uuid.time_v7(v7_a) + time_b := uuid.time_v7(v7_b) + time_c := uuid.time_v7(v7_c) + + log.debugf("A: %02x, %v", v7_a, time_a) + log.debugf("B: %02x, %v", v7_b, time_b) + log.debugf("C: %02x, %v", v7_c, time_c) + + testing.expect(t, time.diff(time_a, time_b) > 0, "The time on the later-generated v7 UUID is earlier than its successor.") + testing.expect(t, time.diff(time_b, time_c) > 0, "The time on the later-generated v7 UUID is earlier than its successor.") + testing.expect(t, time.diff(time_a, time_c) > 0, "The time on the later-generated v7 UUID is earlier than its successor.") + + v7_with_counter := uuid.generate_v7(0x555) + log.debugf("D: %02x", v7_with_counter) + testing.expect_value(t, uuid.counter_v7(v7_with_counter), 0x555) +} + +@(test) +test_sorting_v1 :: proc(t: ^testing.T) { + // This test is to make sure that the v1 UUIDs do _not_ sort. + // They are incapable of sorting properly by the nature their time bit ordering. + // + // Something is very strange if they do sort correctly. + point_a := time.unix(1, 0) + point_b := time.unix(3, 0) + point_c := time.unix(5, 0) + point_d := time.unix(7, 0) + point_e := time.unix(11, 0) + + mac: [6]byte + v1_a := uuid.generate_v1(0, mac, point_a) + v1_b := uuid.generate_v1(0, mac, point_b) + v1_c := uuid.generate_v1(0, mac, point_c) + v1_d := uuid.generate_v1(0, mac, point_d) + v1_e := uuid.generate_v1(0, mac, point_e) + + sort_test := [5]u128be { + transmute(u128be)v1_e, + transmute(u128be)v1_a, + transmute(u128be)v1_d, + transmute(u128be)v1_b, + transmute(u128be)v1_c, + } + + log.debugf("Before: %x", sort_test) + slice.sort(sort_test[:]) + log.debugf("After: %x", sort_test) + + ERROR :: "v1 UUIDs are sorting by time, despite this not being possible." + + testing.expect(t, sort_test[0] != transmute(u128be)v1_a, ERROR) + testing.expect(t, sort_test[1] != transmute(u128be)v1_b, ERROR) + testing.expect(t, sort_test[2] != transmute(u128be)v1_c, ERROR) + testing.expect(t, sort_test[3] != transmute(u128be)v1_d, ERROR) + testing.expect(t, sort_test[4] != transmute(u128be)v1_e, ERROR) +} + +@(test) +test_sorting_v6 :: proc(t: ^testing.T) { + context.random_generator = crypto.random_generator() + + point_a := time.unix(1, 0) + point_b := time.unix(3, 0) + point_c := time.unix(5, 0) + point_d := time.unix(7, 0) + point_e := time.unix(11, 0) + + mac: [6]byte + v6_a := uuid.generate_v6(0, mac, point_a) + v6_b := uuid.generate_v6(0, mac, point_b) + v6_c := uuid.generate_v6(0, mac, point_c) + v6_d := uuid.generate_v6(0, mac, point_d) + v6_e := uuid.generate_v6(0, mac, point_e) + + sort_test := [5]u128be { + transmute(u128be)v6_e, + transmute(u128be)v6_a, + transmute(u128be)v6_d, + transmute(u128be)v6_b, + transmute(u128be)v6_c, + } + + log.debugf("Before: %x", sort_test) + slice.sort(sort_test[:]) + log.debugf("After: %x", sort_test) + + ERROR :: "v6 UUIDs are failing to sort properly." + + testing.expect(t, sort_test[0] < sort_test[1], ERROR) + testing.expect(t, sort_test[1] < sort_test[2], ERROR) + testing.expect(t, sort_test[2] < sort_test[3], ERROR) + testing.expect(t, sort_test[3] < sort_test[4], ERROR) + + testing.expect(t, sort_test[0] == transmute(u128be)v6_a, ERROR) + testing.expect(t, sort_test[1] == transmute(u128be)v6_b, ERROR) + testing.expect(t, sort_test[2] == transmute(u128be)v6_c, ERROR) + testing.expect(t, sort_test[3] == transmute(u128be)v6_d, ERROR) + testing.expect(t, sort_test[4] == transmute(u128be)v6_e, ERROR) +} + +@(test) +test_sorting_v7 :: proc(t: ^testing.T) { + context.random_generator = crypto.random_generator() + + point_a := time.unix(1, 0) + point_b := time.unix(3, 0) + point_c := time.unix(5, 0) + point_d := time.unix(7, 0) + point_e := time.unix(11, 0) + + v7_a := uuid.generate_v7(point_a) + v7_b := uuid.generate_v7(point_b) + v7_c := uuid.generate_v7(point_c) + v7_d := uuid.generate_v7(point_d) + v7_e := uuid.generate_v7(point_e) + + sort_test := [5]u128be { + transmute(u128be)v7_e, + transmute(u128be)v7_a, + transmute(u128be)v7_d, + transmute(u128be)v7_b, + transmute(u128be)v7_c, + } + + log.debugf("Before: %x", sort_test) + slice.sort(sort_test[:]) + log.debugf("After: %x", sort_test) + + ERROR :: "v7 UUIDs are failing to sort properly." + + testing.expect(t, sort_test[0] < sort_test[1], ERROR) + testing.expect(t, sort_test[1] < sort_test[2], ERROR) + testing.expect(t, sort_test[2] < sort_test[3], ERROR) + testing.expect(t, sort_test[3] < sort_test[4], ERROR) + + testing.expect(t, sort_test[0] == transmute(u128be)v7_a, ERROR) + testing.expect(t, sort_test[1] == transmute(u128be)v7_b, ERROR) + testing.expect(t, sort_test[2] == transmute(u128be)v7_c, ERROR) + testing.expect(t, sort_test[3] == transmute(u128be)v7_d, ERROR) + testing.expect(t, sort_test[4] == transmute(u128be)v7_e, ERROR) +} + +@(test) +test_writing :: proc(t: ^testing.T) { + id: uuid.Identifier + + for &b, i in id { + b = u8(i) + } + + buf: [uuid.EXPECTED_LENGTH]u8 + + s_alloc := uuid.to_string(id) + defer delete(s_alloc) + + s_buf := uuid.to_string(id, buf[:]) + + testing.expect_value(t, s_alloc, "00010203-0405-0607-0809-0a0b0c0d0e0f") + testing.expect_value(t, s_buf, "00010203-0405-0607-0809-0a0b0c0d0e0f") +} + +@(test) +test_reading :: proc(t: ^testing.T) { + id, err := uuid.read("00010203-0405-0607-0809-0a0b0c0d0e0f") + testing.expect_value(t, err, nil) + + for b, i in id { + testing.expect_value(t, b, u8(i)) + } +} + +@(test) +test_reading_errors :: proc(t: ^testing.T) { + { + BAD_STRING :: "|.......@....@....@....@............" + _, err := uuid.read(BAD_STRING) + testing.expect_value(t, err, uuid.Read_Error.Invalid_Separator) + } + + { + BAD_STRING :: "|.......-....-....-....-............" + _, err := uuid.read(BAD_STRING) + testing.expect_value(t, err, uuid.Read_Error.Invalid_Hexadecimal) + } + + { + BAD_STRING :: ".......-....-....-....-............" + _, err := uuid.read(BAD_STRING) + testing.expect_value(t, err, uuid.Read_Error.Invalid_Length) + } + + { + BAD_STRING :: "|.......-....-....-....-............|" + _, err := uuid.read(BAD_STRING) + testing.expect_value(t, err, uuid.Read_Error.Invalid_Length) + } + + { + BAD_STRING :: "00000000-0000-0000-0000-0000000000001" + _, err := uuid.read(BAD_STRING) + testing.expect_value(t, err, uuid.Read_Error.Invalid_Length) + } + + { + BAD_STRING :: "00000000000000000000000000000000" + _, err := uuid.read(BAD_STRING) + testing.expect_value(t, err, uuid.Read_Error.Invalid_Length) + } + + { + OK_STRING :: "00000000-0000-0000-0000-000000000000" + _, err := uuid.read(OK_STRING) + testing.expect_value(t, err, nil) + } +} diff --git a/tests/core/encoding/varint/test_core_varint.odin b/tests/core/encoding/varint/test_core_varint.odin index ee1798aa7..5058f3022 100644 --- a/tests/core/encoding/varint/test_core_varint.odin +++ b/tests/core/encoding/varint/test_core_varint.odin @@ -2,110 +2,74 @@ package test_core_varint import "core:encoding/varint" import "core:testing" -import "core:fmt" -import "core:os" import "core:slice" import "core:math/rand" -TEST_count := 0 -TEST_fail := 0 - -RANDOM_TESTS :: 100 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - test_leb128(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} +NUM_RANDOM_TESTS_PER_BYTE_SIZE :: 10_000 @(test) -test_leb128 :: proc(t: ^testing.T) { +test_uleb :: proc(t: ^testing.T) { buf: [varint.LEB128_MAX_BYTES]u8 for vector in ULEB_Vectors { val, size, err := varint.decode_uleb128(vector.encoded) - msg := fmt.tprintf("Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size) - expect(t, size == vector.size && val == vector.value, msg) - - msg = fmt.tprintf("Expected decoder to return error %v, got %v for vector %v", vector.error, err, vector) - expect(t, err == vector.error, msg) + testing.expectf(t, size == vector.size && val == vector.value, "Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size) + testing.expectf(t, err == vector.error, "Expected decoder to return error %v, got %v for vector %v", vector.error, err, vector) if err == .None { // Try to roundtrip size, err = varint.encode_uleb128(buf[:], vector.value) - msg = fmt.tprintf("Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size]) - expect(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), msg) + testing.expectf(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), "Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size]) } } +} + +@(test) +test_ileb :: proc(t: ^testing.T) { + buf: [varint.LEB128_MAX_BYTES]u8 for vector in ILEB_Vectors { val, size, err := varint.decode_ileb128(vector.encoded) - msg := fmt.tprintf("Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size) - expect(t, size == vector.size && val == vector.value, msg) - - msg = fmt.tprintf("Expected decoder to return error %v, got %v", vector.error, err) - expect(t, err == vector.error, msg) + testing.expectf(t, size == vector.size && val == vector.value, "Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size) + testing.expectf(t, err == vector.error, "Expected decoder to return error %v, got %v", vector.error, err) if err == .None { // Try to roundtrip size, err = varint.encode_ileb128(buf[:], vector.value) - msg = fmt.tprintf("Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size]) - expect(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), msg) + testing.expectf(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), "Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size]) } } +} + +@(test) +test_random :: proc(t: ^testing.T) { + buf: [varint.LEB128_MAX_BYTES]u8 for num_bytes in 1..=uint(16) { - for _ in 0..=RANDOM_TESTS { + for _ in 0..=NUM_RANDOM_TESTS_PER_BYTE_SIZE { unsigned, signed := get_random(num_bytes) - { encode_size, encode_err := varint.encode_uleb128(buf[:], unsigned) - msg := fmt.tprintf("%v failed to encode as an unsigned LEB128 value, got %v", unsigned, encode_err) - expect(t, encode_err == .None, msg) + testing.expectf(t, encode_err == .None, "%v failed to encode as an unsigned LEB128 value, got %v", unsigned, encode_err) decoded, decode_size, decode_err := varint.decode_uleb128(buf[:]) - msg = fmt.tprintf("Expected %02x to decode as %v, got %v", buf[:encode_size], unsigned, decoded) - expect(t, decode_err == .None && decode_size == encode_size && decoded == unsigned, msg) + testing.expectf(t, decode_err == .None && decode_size == encode_size && decoded == unsigned, "Expected %02x to decode as %v, got %v", buf[:encode_size], unsigned, decoded) } { encode_size, encode_err := varint.encode_ileb128(buf[:], signed) - msg := fmt.tprintf("%v failed to encode as a signed LEB128 value, got %v", signed, encode_err) - expect(t, encode_err == .None, msg) + testing.expectf(t, encode_err == .None, "%v failed to encode as a signed LEB128 value, got %v", signed, encode_err) decoded, decode_size, decode_err := varint.decode_ileb128(buf[:]) - msg = fmt.tprintf("Expected %02x to decode as %v, got %v, err: %v", buf[:encode_size], signed, decoded, decode_err) - expect(t, decode_err == .None && decode_size == encode_size && decoded == signed, msg) + testing.expectf(t, decode_err == .None && decode_size == encode_size && decoded == signed, "Expected %02x to decode as %v, got %v, err: %v", buf[:encode_size], signed, decoded, decode_err) } } } } +@(private) get_random :: proc(byte_count: uint) -> (u: u128, i: i128) { assert(byte_count >= 0 && byte_count <= size_of(u128)) diff --git a/tests/core/encoding/xml/test_core_xml.odin b/tests/core/encoding/xml/test_core_xml.odin index c62033491..b29431e10 100644 --- a/tests/core/encoding/xml/test_core_xml.odin +++ b/tests/core/encoding/xml/test_core_xml.odin @@ -2,10 +2,10 @@ package test_core_xml import "core:encoding/xml" import "core:testing" -import "core:mem" import "core:strings" import "core:io" import "core:fmt" +import "core:log" import "core:hash" Silent :: proc(pos: xml.Pos, format: string, args: ..any) {} @@ -14,9 +14,6 @@ OPTIONS :: xml.Options{ flags = { .Ignore_Unsupported, .Intern_Comments, }, expected_doctype = "", } -TEST_count := 0 -TEST_fail := 0 - TEST :: struct { filename: string, options: xml.Options, @@ -24,22 +21,14 @@ TEST :: struct { crc32: u32, } -/* - Relative to ODIN_ROOT -*/ -TEST_FILE_PATH_PREFIX :: "tests/core/assets" +TEST_SUITE_PATH :: ODIN_ROOT + "tests/core/assets/" -TESTS :: []TEST{ - /* - First we test that certain files parse without error. - */ - - { - /* - Tests UTF-8 idents and values. - Test namespaced ident. - Tests that nested partial CDATA start doesn't trip up parser. - */ +@(test) +xml_test_utf8_normal :: proc(t: ^testing.T) { + run_test(t, { + // Tests UTF-8 idents and values. + // Test namespaced ident. + // Tests that nested partial CDATA start doesn't trip up parser. filename = "XML/utf8.xml", options = { flags = { @@ -47,14 +36,15 @@ TESTS :: []TEST{ }, expected_doctype = "恥ずべきフクロウ", }, - crc32 = 0xe9b62f03, - }, + crc32 = 0xefa55f27, + }) +} - { - /* - Same as above. - Unbox CDATA in data tag. - */ +@(test) +xml_test_utf8_unbox_cdata :: proc(t: ^testing.T) { + run_test(t, { + // Same as above. + // Unbox CDATA in data tag. filename = "XML/utf8.xml", options = { flags = { @@ -62,14 +52,15 @@ TESTS :: []TEST{ }, expected_doctype = "恥ずべきフクロウ", }, - crc32 = 0x9c2643ed, - }, + crc32 = 0x2dd27770, + }) +} - { - /* - Simple Qt TS translation file. - `core:i18n` requires it to be parsed properly. - */ +@(test) +xml_test_nl_qt_ts :: proc(t: ^testing.T) { + run_test(t, { + // Simple Qt TS translation file. + // `core:i18n` requires it to be parsed properly. filename = "I18N/nl_NL-qt-ts.ts", options = { flags = { @@ -78,13 +69,14 @@ TESTS :: []TEST{ expected_doctype = "TS", }, crc32 = 0x859b7443, - }, + }) +} - { - /* - Simple XLiff 1.2 file. - `core:i18n` requires it to be parsed properly. - */ +@(test) +xml_test_xliff_1_2 :: proc(t: ^testing.T) { + run_test(t, { + // Simple XLiff 1.2 file. + // `core:i18n` requires it to be parsed properly. filename = "I18N/nl_NL-xliff-1.2.xliff", options = { flags = { @@ -93,13 +85,14 @@ TESTS :: []TEST{ expected_doctype = "xliff", }, crc32 = 0x3deaf329, - }, + }) +} - { - /* - Simple XLiff 2.0 file. - `core:i18n` requires it to be parsed properly. - */ +@(test) +xml_test_xliff_2_0 :: proc(t: ^testing.T) { + run_test(t, { + // Simple XLiff 2.0 file. + // `core:i18n` requires it to be parsed properly. filename = "I18N/nl_NL-xliff-2.0.xliff", options = { flags = { @@ -108,9 +101,12 @@ TESTS :: []TEST{ expected_doctype = "xliff", }, crc32 = 0x0c55e287, - }, + }) +} - { +@(test) +xml_test_entities :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/entities.html", options = { flags = { @@ -119,9 +115,12 @@ TESTS :: []TEST{ expected_doctype = "html", }, crc32 = 0x05373317, - }, + }) +} - { +@(test) +xml_test_entities_unbox :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/entities.html", options = { flags = { @@ -129,10 +128,13 @@ TESTS :: []TEST{ }, expected_doctype = "html", }, - crc32 = 0x3b6d4a90, - }, + crc32 = 0x350ca83e, + }) +} - { +@(test) +xml_test_entities_unbox_decode :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/entities.html", options = { flags = { @@ -140,13 +142,27 @@ TESTS :: []TEST{ }, expected_doctype = "html", }, - crc32 = 0x5be2ffdc, - }, + crc32 = 0x7f58db7d, + }) +} - /* - Then we test that certain errors are returned as expected. - */ - { +@(test) +xml_test_attribute_whitespace :: proc(t: ^testing.T) { + run_test(t, { + // Same as above. + // Unbox CDATA in data tag. + filename = "XML/attribute-whitespace.xml", + options = { + flags = {}, + expected_doctype = "foozle", + }, + crc32 = 0x8f5fd6c1, + }) +} + +@(test) +xml_test_invalid_doctype :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/utf8.xml", options = { flags = { @@ -156,12 +172,12 @@ TESTS :: []TEST{ }, err = .Invalid_DocType, crc32 = 0x49b83d0a, - }, + }) +} - /* - Parse the 9.08 MiB unicode.xml for good measure. - */ - { +@(test) +xml_test_unicode :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/unicode.xml", options = { flags = { @@ -170,40 +186,38 @@ TESTS :: []TEST{ expected_doctype = "", }, err = .None, - crc32 = 0x0b6100ab, - }, + crc32 = 0x73070b55, + }) } -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] LOG:\n\t%v\n", loc, v) +@(private) +run_test :: proc(t: ^testing.T, test: TEST) { + path := strings.concatenate({TEST_SUITE_PATH, test.filename}) + defer delete(path) + + doc, err := xml.load_from_file(path, test.options, Silent) + defer xml.destroy(doc) + + tree_string := doc_to_string(doc) + tree_bytes := transmute([]u8)tree_string + defer delete(tree_bytes) + + crc32 := hash.crc32(tree_bytes) + + failed := err != test.err + testing.expectf(t, err == test.err, "%v: Expected return value %v, got %v", test.filename, test.err, err) + + failed |= crc32 != test.crc32 + testing.expectf(t, crc32 == test.crc32, "%v: Expected CRC 0x%08x, got 0x%08x, with options %v", test.filename, test.crc32, crc32, test.options) + + if failed { + // Don't fully print big trees. + tree_string = tree_string[:min(2_048, len(tree_string))] + log.error(tree_string) } } -test_file_path :: proc(filename: string) -> (path: string) { - - path = fmt.tprintf("%v%v/%v", ODIN_ROOT, TEST_FILE_PATH_PREFIX, filename) - temp := transmute([]u8)path - - for r, i in path { - if r == '\\' { - temp[i] = '/' - } - } - return path -} - +@(private) doc_to_string :: proc(doc: ^xml.Document) -> (result: string) { /* Effectively a clone of the debug printer in the xml package. @@ -284,56 +298,4 @@ doc_to_string :: proc(doc: ^xml.Document) -> (result: string) { print(strings.to_writer(&buf), doc) return strings.clone(strings.to_string(buf)) -} - -@test -run_tests :: proc(t: ^testing.T) { - for test in TESTS { - path := test_file_path(test.filename) - log(t, fmt.tprintf("Trying to parse %v", path)) - - doc, err := xml.load_from_file(path, test.options, Silent) - defer xml.destroy(doc) - - tree_string := doc_to_string(doc) - tree_bytes := transmute([]u8)tree_string - defer delete(tree_bytes) - - crc32 := hash.crc32(tree_bytes) - - failed := err != test.err - err_msg := fmt.tprintf("Expected return value %v, got %v", test.err, err) - expect(t, err == test.err, err_msg) - - failed |= crc32 != test.crc32 - err_msg = fmt.tprintf("Expected CRC 0x%08x, got 0x%08x, with options %v", test.crc32, crc32, test.options) - expect(t, crc32 == test.crc32, err_msg) - - if failed { - /* - Don't fully print big trees. - */ - tree_string = tree_string[:min(2_048, len(tree_string))] - fmt.println(tree_string) - } - } -} - -main :: proc() { - t := testing.T{} - - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - run_tests(&t) - - if len(track.allocation_map) > 0 { - for _, v in track.allocation_map { - err_msg := fmt.tprintf("%v Leaked %v bytes.", v.location, v.size) - expect(&t, false, err_msg) - } - } - - fmt.printf("\n%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) } \ No newline at end of file diff --git a/tests/core/flags/test_core_flags.odin b/tests/core/flags/test_core_flags.odin new file mode 100644 index 000000000..e32c6832c --- /dev/null +++ b/tests/core/flags/test_core_flags.odin @@ -0,0 +1,1393 @@ +package test_core_flags + +import "base:runtime" +import "core:bytes" +import "core:flags" +import "core:fmt" +@require import "core:log" +import "core:math" +@require import "core:net" +import "core:os" +import "core:strings" +import "core:testing" +import "core:time/datetime" + +@(test) +test_no_args :: proc(t: ^testing.T) { + S :: struct { + a: string, + } + s: S + args: []string + result := flags.parse(&s, args) + testing.expect_value(t, result, nil) +} + +@(test) +test_two_flags :: proc(t: ^testing.T) { + S :: struct { + i: string, + o: string, + } + s: S + args := [?]string { "-i:hellope", "-o:world" } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.i, "hellope") + testing.expect_value(t, s.o, "world") +} + +@(test) +test_extra_arg :: proc(t: ^testing.T) { + S :: struct { + a: string, + } + s: S + args := [?]string { "-a:hellope", "world" } + result := flags.parse(&s, args[:]) + err, ok := result.(flags.Parse_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Extra_Positional) + } +} + +@(test) +test_assignment_oddities :: proc(t: ^testing.T) { + S :: struct { + s: string, + } + s: S + + { + args := [?]string { "-s:=" } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.s, "=") + } + + { + args := [?]string { "-s=:" } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.s, ":") + } + + { + args := [?]string { "-" } + result := flags.parse(&s, args[:]) + err, ok := result.(flags.Parse_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.No_Flag) + } + } +} + +@(test) +test_string_into_int :: proc(t: ^testing.T) { + S :: struct { + n: int, + } + s: S + args := [?]string { "-n:hellope" } + result := flags.parse(&s, args[:]) + err, ok := result.(flags.Parse_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Bad_Value) + } +} + +@(test) +test_string_into_bool :: proc(t: ^testing.T) { + S :: struct { + b: bool, + } + s: S + args := [?]string { "-b:hellope" } + result := flags.parse(&s, args[:]) + err, ok := result.(flags.Parse_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Bad_Value) + } +} + +@(test) +test_all_bools :: proc(t: ^testing.T) { + S :: struct { + a: bool, + b: b8, + c: b16, + d: b32, + e: b64, + } + s: S + s.a = true + s.c = true + args := [?]string { "-a:false", "-b:true", "-c:0", "-d", "-e:1" } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a, false) + testing.expect_value(t, s.b, true) + testing.expect_value(t, s.c, false) + testing.expect_value(t, s.d, true) + testing.expect_value(t, s.e, true) +} + +@(test) +test_all_ints :: proc(t: ^testing.T) { + S :: struct { + a: u8, + b: i8, + c: u16, + d: i16, + e: u32, + f: i32, + g: u64, + i: i64, + j: u128, + k: i128, + } + + s: S + args := [?]string { + fmt.tprintf("-a:%i", max(u8)), + fmt.tprintf("-b:%i", min(i8)), + fmt.tprintf("-c:%i", max(u16)), + fmt.tprintf("-d:%i", min(i16)), + fmt.tprintf("-e:%i", max(u32)), + fmt.tprintf("-f:%i", min(i32)), + fmt.tprintf("-g:%i", max(u64)), + fmt.tprintf("-i:%i", min(i64)), + fmt.tprintf("-j:%i", max(u128)), + fmt.tprintf("-k:%i", min(i128)), + } + + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a, max(u8)) + testing.expect_value(t, s.b, min(i8)) + testing.expect_value(t, s.c, max(u16)) + testing.expect_value(t, s.d, min(i16)) + testing.expect_value(t, s.e, max(u32)) + testing.expect_value(t, s.f, min(i32)) + testing.expect_value(t, s.g, max(u64)) + testing.expect_value(t, s.i, min(i64)) + testing.expect_value(t, s.j, max(u128)) + testing.expect_value(t, s.k, min(i128)) +} + +@(test) +test_all_floats :: proc(t: ^testing.T) { + S :: struct { + a: f16, + b: f32, + c: f64, + d: f64, + e: f64, + } + s: S + args := [?]string { "-a:100", "-b:3.14", "-c:-123.456", "-d:nan", "-e:inf" } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a, 100) + testing.expect_value(t, s.b, 3.14) + testing.expect_value(t, s.c, -123.456) + testing.expectf(t, math.is_nan(s.d), "expected NaN, got %v", s.d) + testing.expectf(t, math.is_inf(s.e, +1), "expected +Inf, got %v", s.e) +} + +@(test) +test_all_enums :: proc(t: ^testing.T) { + E :: enum { A, B } + S :: struct { + nameless: enum { C, D }, + named: E, + } + s: S + args := [?]string { "-nameless:D", "-named:B" } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, cast(int)s.nameless, 1) + testing.expect_value(t, s.named, E.B) +} + +@(test) +test_all_complex :: proc(t: ^testing.T) { + S :: struct { + a: complex32, + b: complex64, + c: complex128, + } + s: S + args := [?]string { "-a:1+0i", "-b:3+7i", "-c:NaNNaNi" } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, real(s.a), 1) + testing.expect_value(t, imag(s.a), 0) + testing.expect_value(t, real(s.b), 3) + testing.expect_value(t, imag(s.b), 7) + testing.expectf(t, math.is_nan(real(s.c)), "expected NaN, got %v", real(s.c)) + testing.expectf(t, math.is_nan(imag(s.c)), "expected NaN, got %v", imag(s.c)) +} + +@(test) +test_all_quaternion :: proc(t: ^testing.T) { + S :: struct { + a: quaternion64, + b: quaternion128, + c: quaternion256, + } + s: S + args := [?]string { "-a:1+0i+1j+0k", "-b:3+7i+5j-3k", "-c:NaNNaNi+Infj-Infk" } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + + raw_a := (cast(^runtime.Raw_Quaternion64)&s.a) + raw_b := (cast(^runtime.Raw_Quaternion128)&s.b) + raw_c := (cast(^runtime.Raw_Quaternion256)&s.c) + + testing.expect_value(t, raw_a.real, 1) + testing.expect_value(t, raw_a.imag, 0) + testing.expect_value(t, raw_a.jmag, 1) + testing.expect_value(t, raw_a.kmag, 0) + + testing.expect_value(t, raw_b.real, 3) + testing.expect_value(t, raw_b.imag, 7) + testing.expect_value(t, raw_b.jmag, 5) + testing.expect_value(t, raw_b.kmag, -3) + + testing.expectf(t, math.is_nan(raw_c.real), "expected NaN, got %v", raw_c.real) + testing.expectf(t, math.is_nan(raw_c.imag), "expected NaN, got %v", raw_c.imag) + testing.expectf(t, math.is_inf(raw_c.jmag, +1), "expected +Inf, got %v", raw_c.jmag) + testing.expectf(t, math.is_inf(raw_c.kmag, -1), "expected -Inf, got %v", raw_c.kmag) +} + +@(test) +test_all_bit_sets :: proc(t: ^testing.T) { + E :: enum { + Option_A, + Option_B, + } + S :: struct { + a: bit_set[0..<8], + b: bit_set[0..<16; u16], + c: bit_set[16..<18; rune], + d: bit_set[0..<1; i8], + e: bit_set[0..<128], + f: bit_set[-32..<32], + g: bit_set[E], + i: bit_set[E; u8], + } + s: S + { + args := [?]string { + "-a:10101", + "-b:0000_0000_0000_0001", + "-c:11", + "-d:___1", + "-e:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "-f:1", + "-g:01", + "-i:1", + } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a, bit_set[0..<8]{0, 2, 4}) + testing.expect_value(t, s.b, bit_set[0..<16; u16]{15}) + testing.expect_value(t, s.c, bit_set[16..<18; rune]{16, 17}) + testing.expect_value(t, s.d, bit_set[0..<1; i8]{0}) + testing.expect_value(t, s.e, bit_set[0..<128]{127}) + testing.expect_value(t, s.f, bit_set[-32..<32]{-32}) + testing.expect_value(t, s.g, bit_set[E]{E.Option_B}) + testing.expect_value(t, s.i, bit_set[E; u8]{E.Option_A}) + } + { + args := [?]string { "-d:11" } + result := flags.parse(&s, args[:]) + err, ok := result.(flags.Parse_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Bad_Value) + } + } +} + +@(test) +test_all_strings :: proc(t: ^testing.T) { + S :: struct { + a, b, c: string, + d: cstring, + } + s: S + args := [?]string { "-a:hi", "-b:hellope", "-c:spaced out", "-d:cstr", "-d:cstr-overwrite" } + result := flags.parse(&s, args[:]) + defer delete(s.d) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a, "hi") + testing.expect_value(t, s.b, "hellope") + testing.expect_value(t, s.c, "spaced out") + testing.expect_value(t, s.d, "cstr-overwrite") +} + +@(test) +test_runes :: proc(t: ^testing.T) { + S :: struct { + a, b, c: rune, + } + s: S + args := [?]string { "-a:a", "-b:ツ", "-c:\U0010FFFF" } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a, 'a') + testing.expect_value(t, s.b, 'ツ') + testing.expect_value(t, s.c, '\U0010FFFF') +} + +@(test) +test_no_value :: proc(t: ^testing.T) { + S :: struct { + a: rune, + } + s: S + + { + args := [?]string { "-a:" } + result := flags.parse(&s, args[:]) + err, ok := result.(flags.Parse_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.No_Value) + } + } + + { + args := [?]string { "-a=" } + result := flags.parse(&s, args[:]) + err, ok := result.(flags.Parse_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.No_Value) + } + } +} + +@(test) +test_overflow :: proc(t: ^testing.T) { + S :: struct { + a: u8, + } + s: S + args := [?]string { "-a:256" } + result := flags.parse(&s, args[:]) + err, ok := result.(flags.Parse_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Bad_Value) + } +} + +@(test) +test_underflow :: proc(t: ^testing.T) { + S :: struct { + a: i8, + } + s: S + args := [?]string { "-a:-129" } + result := flags.parse(&s, args[:]) + err, ok := result.(flags.Parse_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Bad_Value) + } +} + +@(test) +test_arrays :: proc(t: ^testing.T) { + S :: struct { + a: [dynamic]string, + b: [dynamic]int, + } + s: S + args := [?]string { "-a:abc", "-b:1", "-a:foo", "-b:3" } + result := flags.parse(&s, args[:]) + defer { + delete(s.a) + delete(s.b) + } + testing.expect_value(t, result, nil) + testing.expect_value(t, len(s.a), 2) + testing.expect_value(t, len(s.b), 2) + + if len(s.a) < 2 || len(s.b) < 2 { + return + } + + testing.expect_value(t, s.a[0], "abc") + testing.expect_value(t, s.a[1], "foo") + testing.expect_value(t, s.b[0], 1) + testing.expect_value(t, s.b[1], 3) +} + +@(test) +test_varargs :: proc(t: ^testing.T) { + S :: struct { + varg: [dynamic]string, + } + s: S + args := [?]string { "abc", "foo", "bar" } + result := flags.parse(&s, args[:]) + defer delete(s.varg) + testing.expect_value(t, result, nil) + testing.expect_value(t, len(s.varg), 3) + + if len(s.varg) < 3 { + return + } + + testing.expect_value(t, s.varg[0], "abc") + testing.expect_value(t, s.varg[1], "foo") + testing.expect_value(t, s.varg[2], "bar") +} + +@(test) +test_mixed_varargs :: proc(t: ^testing.T) { + S :: struct { + input: string `args:"pos=0"`, + varg: [dynamic]string, + } + s: S + args := [?]string { "abc", "foo", "bar" } + result := flags.parse(&s, args[:]) + defer delete(s.varg) + testing.expect_value(t, result, nil) + testing.expect_value(t, len(s.varg), 2) + + if len(s.varg) < 2 { + return + } + + testing.expect_value(t, s.input, "abc") + testing.expect_value(t, s.varg[0], "foo") + testing.expect_value(t, s.varg[1], "bar") +} + +@(test) +test_maps :: proc(t: ^testing.T) { + S :: struct { + a: map[string]string, + b: map[string]int, + } + s: S + args := [?]string { "-a:abc=foo", "-b:bar=42" } + result := flags.parse(&s, args[:]) + defer { + delete(s.a) + delete(s.b) + } + testing.expect_value(t, result, nil) + testing.expect_value(t, len(s.a), 1) + testing.expect_value(t, len(s.b), 1) + + if len(s.a) < 1 || len(s.b) < 1 { + return + } + + abc, has_abc := s.a["abc"] + bar, has_bar := s.b["bar"] + + testing.expect(t, has_abc, "expected map to have `abc` key set") + testing.expect(t, has_bar, "expected map to have `bar` key set") + testing.expect_value(t, abc, "foo") + testing.expect_value(t, bar, 42) +} + +@(test) +test_invalid_map_syntax :: proc(t: ^testing.T) { + S :: struct { + a: map[string]string, + } + s: S + args := [?]string { "-a:foo:42" } + result := flags.parse(&s, args[:]) + err, ok := result.(flags.Parse_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.No_Value) + } +} + +@(test) +test_underline_name_to_dash :: proc(t: ^testing.T) { + S :: struct { + a_b: int, + } + s: S + args := [?]string { "-a-b:3" } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a_b, 3) +} + +@(test) +test_tags_pos :: proc(t: ^testing.T) { + S :: struct { + b: int `args:"pos=1"`, + a: int `args:"pos=0"`, + } + s: S + args := [?]string { "42", "99" } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a, 42) + testing.expect_value(t, s.b, 99) +} + +@(test) +test_tags_name :: proc(t: ^testing.T) { + S :: struct { + a: int `args:"name=alice"`, + b: int `args:"name=bill"`, + } + s: S + args := [?]string { "-alice:1", "-bill:2" } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a, 1) + testing.expect_value(t, s.b, 2) +} + +@(test) +test_tags_required :: proc(t: ^testing.T) { + S :: struct { + a: int, + b: int `args:"required"`, + } + s: S + args := [?]string { "-a:1" } + result := flags.parse(&s, args[:]) + _, ok := result.(flags.Validation_Error) + testing.expectf(t, ok, "unexpected result: %v", result) +} + +@(test) +test_tags_required_pos :: proc(t: ^testing.T) { + S :: struct { + a: int `args:"pos=0,required"`, + b: int `args:"pos=1"`, + } + s: S + args := [?]string { "-b:5" } + result := flags.parse(&s, args[:]) + _, ok := result.(flags.Validation_Error) + testing.expectf(t, ok, "unexpected result: %v", result) +} + +@(test) +test_tags_required_limit_min :: proc(t: ^testing.T) { + S :: struct { + n: [dynamic]int `args:"required=3"`, + } + + { + s: S + args := [?]string { "-n:1" } + result := flags.parse(&s, args[:]) + defer delete(s.n) + _, ok := result.(flags.Validation_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + } + + { + s: S + args := [?]string { "-n:3", "-n:5", "-n:7" } + result := flags.parse(&s, args[:]) + defer delete(s.n) + testing.expect_value(t, result, nil) + testing.expect_value(t, len(s.n), 3) + + if len(s.n) == 3 { + testing.expect_value(t, s.n[0], 3) + testing.expect_value(t, s.n[1], 5) + testing.expect_value(t, s.n[2], 7) + } + } +} + +@(test) +test_tags_required_limit_min_max :: proc(t: ^testing.T) { + S :: struct { + n: [dynamic]int `args:"required=2<4"`, + } + + { + s: S + args := [?]string { "-n:1" } + result := flags.parse(&s, args[:]) + defer delete(s.n) + _, ok := result.(flags.Validation_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + } + + { + s: S + args := [?]string { "-n:1", "-n:2", "-n:3", "-n:4" } + result := flags.parse(&s, args[:]) + defer delete(s.n) + _, ok := result.(flags.Validation_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + } + + { + s: S + args := [?]string { "-n:3", "-n:5", "-n:7" } + result := flags.parse(&s, args[:]) + defer delete(s.n) + testing.expect_value(t, result, nil) + testing.expect_value(t, len(s.n), 3) + + if len(s.n) == 3 { + testing.expect_value(t, s.n[0], 3) + testing.expect_value(t, s.n[1], 5) + testing.expect_value(t, s.n[2], 7) + } + } +} + +@(test) +test_tags_required_limit_max :: proc(t: ^testing.T) { + S :: struct { + n: [dynamic]int `args:"required=<4"`, + } + + { + s: S + args: []string + result := flags.parse(&s, args) + testing.expect_value(t, result, nil) + } + + { + s: S + args := [?]string { "-n:1", "-n:2", "-n:3", "-n:4" } + result := flags.parse(&s, args[:]) + defer delete(s.n) + _, ok := result.(flags.Validation_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + } + + { + s: S + args := [?]string { "-n:3", "-n:5", "-n:7" } + result := flags.parse(&s, args[:]) + defer delete(s.n) + testing.expect_value(t, result, nil) + testing.expect_value(t, len(s.n), 3) + + if len(s.n) == 3 { + testing.expect_value(t, s.n[0], 3) + testing.expect_value(t, s.n[1], 5) + testing.expect_value(t, s.n[2], 7) + } + } +} + +@(test) +test_tags_pos_out_of_order :: proc(t: ^testing.T) { + S :: struct { + a: int `args:"pos=2"`, + varg: [dynamic]int, + } + s: S + args := [?]string { "1", "2", "3", "4" } + result := flags.parse(&s, args[:]) + defer delete(s.varg) + testing.expect_value(t, result, nil) + testing.expect_value(t, len(s.varg), 3) + + if len(s.varg) < 3 { + return + } + + testing.expect_value(t, s.a, 3) + testing.expect_value(t, s.varg[0], 1) + testing.expect_value(t, s.varg[1], 2) + testing.expect_value(t, s.varg[2], 4) +} + +@(test) +test_missing_flag :: proc(t: ^testing.T) { + S :: struct { + a: int, + } + s: S + args := [?]string { "-b" } + result := flags.parse(&s, args[:]) + err, ok := result.(flags.Parse_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Missing_Flag) + } +} + +@(test) +test_alt_syntax :: proc(t: ^testing.T) { + S :: struct { + a: int, + } + s: S + args := [?]string { "-a=3" } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a, 3) +} + +@(test) +test_strict_returns_first_error :: proc(t: ^testing.T) { + S :: struct { + b: int, + c: int, + } + s: S + args := [?]string { "-a=3", "-b=3" } + result := flags.parse(&s, args[:], strict=true) + err, ok := result.(flags.Parse_Error) + testing.expect_value(t, s.b, 0) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Missing_Flag) + } +} + +@(test) +test_non_strict_returns_last_error :: proc(t: ^testing.T) { + S :: struct { + a: int, + b: int, + } + s: S + args := [?]string { "-a=foo", "-b=2", "-c=3" } + result := flags.parse(&s, args[:], strict=false) + err, ok := result.(flags.Parse_Error) + testing.expect_value(t, s.b, 2) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Missing_Flag) + } +} + +@(test) +test_map_overwrite :: proc(t: ^testing.T) { + S :: struct { + m: map[string]int, + } + s: S + args := [?]string { "-m:foo=3", "-m:foo=5" } + result := flags.parse(&s, args[:]) + defer delete(s.m) + testing.expect_value(t, result, nil) + testing.expect_value(t, len(s.m), 1) + foo, has_foo := s.m["foo"] + testing.expect(t, has_foo, "expected map to have `foo` key set") + testing.expect_value(t, foo, 5) +} + +@(test) +test_maps_of_arrays :: proc(t: ^testing.T) { + // Why you would ever want to do this, I don't know, but it's possible! + S :: struct { + m: map[string][dynamic]int, + } + s: S + args := [?]string { "-m:foo=1", "-m:foo=2", "-m:bar=3" } + result := flags.parse(&s, args[:]) + defer { + for _, v in s.m { + delete(v) + } + delete(s.m) + } + testing.expect_value(t, result, nil) + testing.expect_value(t, len(s.m), 2) + + if len(s.m) != 2 { + return + } + + foo, has_foo := s.m["foo"] + bar, has_bar := s.m["bar"] + + testing.expect_value(t, has_foo, true) + testing.expect_value(t, has_bar, true) + + if has_foo { + testing.expect_value(t, len(foo), 2) + if len(foo) == 2 { + testing.expect_value(t, foo[0], 1) + testing.expect_value(t, foo[1], 2) + } + } + + if has_bar { + testing.expect_value(t, len(bar), 1) + if len(bar) == 1 { + testing.expect_value(t, bar[0], 3) + } + } +} + +@(test) +test_builtin_help_flag :: proc(t: ^testing.T) { + S :: struct {} + s: S + + args_short := [?]string { "-h" } + args_normal := [?]string { "-help" } + + result := flags.parse(&s, args_short[:]) + _, ok := result.(flags.Help_Request) + testing.expectf(t, ok, "unexpected result: %v", result) + + result = flags.parse(&s, args_normal[:]) + _, ok = result.(flags.Help_Request) + testing.expectf(t, ok, "unexpected result: %v", result) +} + +// This test makes sure that if a positional argument is specified, it won't be +// overwritten by an unspecified positional, which should follow the principle +// of least surprise for the user. +@(test) +test_pos_nonoverlap :: proc(t: ^testing.T) { + S :: struct { + a: int `args:"pos=0"`, + b: int `args:"pos=1"`, + } + s: S + + args := [?]string { "-a:3", "5" } + + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a, 3) + testing.expect_value(t, s.b, 5) +} + +// This test ensures the underlying `bit_array` container handles many +// arguments in a sane manner. +@(test) +test_pos_many_args :: proc(t: ^testing.T) { + S :: struct { + varg: [dynamic]int, + a: int `args:"pos=0,required"`, + b: int `args:"pos=64,required"`, + c: int `args:"pos=66,required"`, + d: int `args:"pos=129,required"`, + } + s: S + + args: [dynamic]string + defer delete(s.varg) + + for i in 0 ..< 130 { append(&args, fmt.aprintf("%i", 1 + i)) } + defer { + for a in args { + delete(a) + } + delete(args) + } + + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + + testing.expect_value(t, s.a, 1) + for i in 1 ..< 63 { testing.expect_value(t, s.varg[i], 2 + i) } + testing.expect_value(t, s.b, 65) + testing.expect_value(t, s.varg[63], 66) + testing.expect_value(t, s.c, 67) + testing.expect_value(t, s.varg[64], 68) + testing.expect_value(t, s.varg[65], 69) + testing.expect_value(t, s.varg[66], 70) + for i in 67 ..< 126 { testing.expect_value(t, s.varg[i], 4 + i) } + testing.expect_value(t, s.d, 130) +} + +@(test) +test_unix :: proc(t: ^testing.T) { + S :: struct { + a: string, + } + s: S + + { + args := [?]string { "--a", "hellope" } + + result := flags.parse(&s, args[:], .Unix) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a, "hellope") + } + + { + args := [?]string { "-a", "hellope", "--a", "world" } + + result := flags.parse(&s, args[:], .Unix) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a, "world") + } + + { + args := [?]string { "-a=hellope" } + + result := flags.parse(&s, args[:], .Unix) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a, "hellope") + } +} + +@(test) +test_unix_variadic :: proc(t: ^testing.T) { + S :: struct { + a: [dynamic]int `args:"variadic"`, + } + s: S + + args := [?]string { "--a", "7", "32", "11" } + + result := flags.parse(&s, args[:], .Unix) + defer delete(s.a) + testing.expect_value(t, result, nil) + testing.expect_value(t, len(s.a), 3) + + if len(s.a) < 3 { + return + } + + testing.expect_value(t, s.a[0], 7) + testing.expect_value(t, s.a[1], 32) + testing.expect_value(t, s.a[2], 11) +} + +@(test) +test_unix_variadic_limited :: proc(t: ^testing.T) { + S :: struct { + a: [dynamic]int `args:"variadic=2"`, + b: int, + } + s: S + + args := [?]string { "-a", "11", "101", "-b", "3" } + + result := flags.parse(&s, args[:], .Unix) + defer delete(s.a) + testing.expect_value(t, result, nil) + testing.expect_value(t, len(s.a), 2) + + if len(s.a) < 2 { + return + } + + testing.expect_value(t, s.a[0], 11) + testing.expect_value(t, s.a[1], 101) + testing.expect_value(t, s.b, 3) +} + +@(test) +test_unix_positional :: proc(t: ^testing.T) { + S :: struct { + a: int `args:"pos=1"`, + b: int `args:"pos=0"`, + } + s: S + + args := [?]string { "-b", "17", "11" } + + result := flags.parse(&s, args[:], .Unix) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a, 11) + testing.expect_value(t, s.b, 17) +} + +@(test) +test_unix_positional_with_variadic :: proc(t: ^testing.T) { + S :: struct { + varg: [dynamic]int, + v: [dynamic]int `args:"variadic"`, + } + s: S + + args := [?]string { "35", "-v", "17", "11" } + + result := flags.parse(&s, args[:], .Unix) + defer { + delete(s.varg) + delete(s.v) + } + testing.expect_value(t, result, nil) + testing.expect_value(t, len(s.varg), 1) + testing.expect_value(t, len(s.v), 2) +} + +@(test) +test_unix_double_dash_variadic :: proc(t: ^testing.T) { + S :: struct { + varg: [dynamic]string, + i: int, + } + s: S + + args := [?]string { "-i", "3", "--", "hellope", "-i", "5" } + + result := flags.parse(&s, args[:], .Unix) + defer { + delete(s.varg) + } + testing.expect_value(t, result, nil) + testing.expect_value(t, len(s.varg), 3) + testing.expect_value(t, s.i, 3) + + if len(s.varg) != 3 { + return + } + + testing.expect_value(t, s.varg[0], "hellope") + testing.expect_value(t, s.varg[1], "-i") + testing.expect_value(t, s.varg[2], "5") +} + +@(test) +test_unix_no_value :: proc(t: ^testing.T) { + S :: struct { + i: int, + } + s: S + + args := [?]string { "--i" } + + result := flags.parse(&s, args[:], .Unix) + err, ok := result.(flags.Parse_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.No_Value) + } +} + +// This test ensures there are no bad frees with cstrings. +@(test) +test_if_dynamic_cstrings_get_freed :: proc(t: ^testing.T) { + S :: struct { + varg: [dynamic]cstring, + } + s: S + + args := [?]string { "Hellope", "world!" } + result := flags.parse(&s, args[:]) + defer { + for v in s.varg { + delete(v) + } + delete(s.varg) + } + testing.expect_value(t, result, nil) +} + +// This test ensures there are no double allocations with cstrings. +@(test) +test_if_map_cstrings_get_freed :: proc(t: ^testing.T) { + S :: struct { + m: map[cstring]cstring, + } + s: S + + args := [?]string { "-m:hellope=world", "-m:hellope=bar", "-m:hellope=foo" } + result := flags.parse(&s, args[:]) + defer { + for _, v in s.m { + delete(v) + } + delete(s.m) + } + testing.expect_value(t, result, nil) + testing.expect_value(t, s.m["hellope"], "foo") +} + +@(test) +test_os_handle :: proc(t: ^testing.T) { + defer if !testing.failed(t) { + // Delete the file now that we're done. + // + // This is not done all the time, just in case the file is useful to debugging. + testing.expect_value(t, os.remove(TEMPORARY_FILENAME), os.ERROR_NONE) + } + + TEMPORARY_FILENAME :: "test_core_flags_write_test_output_data" + + test_data := "Hellope!" + + W :: struct { + outf: os.Handle `args:"file=cw"`, + } + w: W + + args := [?]string { fmt.tprintf("-outf:%s", TEMPORARY_FILENAME) } + result := flags.parse(&w, args[:]) + testing.expect_value(t, result, nil) + if result != nil { + return + } + defer os.close(w.outf) + os.write_string(w.outf, test_data) + + R :: struct { + inf: os.Handle `args:"file=r"`, + } + r: R + + args = [?]string { fmt.tprintf("-inf:%s", TEMPORARY_FILENAME) } + result = flags.parse(&r, args[:]) + testing.expect_value(t, result, nil) + if result != nil { + return + } + defer os.close(r.inf) + data, read_ok := os.read_entire_file_from_handle(r.inf, context.temp_allocator) + testing.expect_value(t, read_ok, true) + file_contents_equal := 0 == bytes.compare(transmute([]u8)test_data, data) + testing.expectf(t, file_contents_equal, "expected file contents to be the same, got %v", data) +} + +@(test) +test_distinct_types :: proc(t: ^testing.T) { + I :: distinct int + S :: struct { + base_i: I `args:"indistinct"`, + unmodified_i: I, + } + s: S + + { + args := [?]string {"-base-i:1"} + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + } + + { + args := [?]string {"-unmodified-i:1"} + result := flags.parse(&s, args[:]) + err, ok := result.(flags.Parse_Error) + testing.expectf(t, ok, "unexpected result: %v", result) + if ok { + testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Unsupported_Type) + } + } +} + +@(test) +test_datetime :: proc(t: ^testing.T) { + when flags.IMPORTING_TIME { + W :: struct { + t: datetime.DateTime, + } + w: W + + args := [?]string { "-t:2024-06-04T12:34:56Z" } + result := flags.parse(&w, args[:]) + testing.expect_value(t, result, nil) + if result != nil { + return + } + testing.expect_value(t, w.t.date.year, 2024) + testing.expect_value(t, w.t.date.month, 6) + testing.expect_value(t, w.t.date.day, 4) + } else { + log.info("Skipping test due to lack of platform support.") + } +} + +@(test) +test_net :: proc(t: ^testing.T) { + when flags.IMPORTING_NET { + W :: struct { + addr: net.Host_Or_Endpoint, + } + w: W + + args := [?]string { "-addr:odin-lang.org:80" } + result := flags.parse(&w, args[:]) + testing.expect_value(t, result, nil) + if result != nil { + return + } + host, is_host := w.addr.(net.Host) + testing.expectf(t, is_host, "expected type of `addr` to be `net.Host`, was %v", w.addr) + testing.expect_value(t, host.hostname, "odin-lang.org") + testing.expect_value(t, host.port, 80) + } else { + log.info("Skipping test due to lack of platform support.") + } +} + +@(test) +test_custom_type_setter :: proc(t: ^testing.T) { + Custom_Bool :: distinct bool + Custom_Data :: struct { + a: int, + } + + S :: struct { + a: Custom_Data, + b: Custom_Bool `args:"indistinct"`, + } + s: S + + // NOTE: Mind that this setter is global state, and the test runner is multi-threaded. + // It should be fine so long as all type setter tests are in this one test proc. + flags.register_type_setter(proc (data: rawptr, data_type: typeid, _, _: string) -> (string, bool, runtime.Allocator_Error) { + if data_type == Custom_Data { + (cast(^Custom_Data)data).a = 32 + return "", true, nil + } + return "", false, nil + }) + defer flags.register_type_setter(nil) + args := [?]string { "-a:hellope", "-b:true" } + result := flags.parse(&s, args[:]) + testing.expect_value(t, result, nil) + testing.expect_value(t, s.a.a, 32) + testing.expect_value(t, s.b, true) +} + +// This test is sensitive to many of the underlying mechanisms of the library, +// so if something isn't working, it'll probably show up here first, but it may +// not be immediately obvious as to what's wrong. +// +// It makes for a good early warning system. +@(test) +test_usage_write_odin :: proc(t: ^testing.T) { + Expected_Output :: `Usage: + varg required-number [number] [name] -bars -bots -foos -gadgets -widgets [-array] [-count] [-greek] [-map-type] [-verbose] ... +Flags: + -required-number:, required | some number + -number: | some other number + -name: + Multi-line documentation + gets formatted + very nicely. + -bars:, exactly 3 | + -bots:, at least 1 | + -foos:, between 2 and 3 | + -gadgets:, at least 1 | + -widgets:, at most 2 | + | + -array:, multiple | + -count: | + -greek: | + -map-type:= | + -verbose | + | +` + + Custom_Enum :: enum { + Alpha, + Omega, + } + + S :: struct { + required_number: int `args:"pos=0,required" usage:"some number"`, + number: int `args:"pos=1" usage:"some other number"`, + name: string `args:"pos=2" usage:" + Multi-line documentation + gets formatted +very nicely. + +"`, + + c: u8 `args:"name=count"`, + greek: Custom_Enum, + + array: [dynamic]rune, + map_type: map[cstring]byte, + + gadgets: [dynamic]string `args:"required=1"`, + widgets: [dynamic]string `args:"required=<3"`, + foos: [dynamic]string `args:"required=2<4"`, + bars: [dynamic]string `args:"required=3<4"`, + bots: [dynamic]string `args:"required"`, + + debug: bool `args:"hidden" usage:"print debug info"`, + verbose: bool, + + varg: [dynamic]string, + } + + builder := strings.builder_make() + defer strings.builder_destroy(&builder) + writer := strings.to_stream(&builder) + flags.write_usage(writer, S, "varg", .Odin) + testing.expect_value(t, strings.to_string(builder), Expected_Output) +} + +@(test) +test_usage_write_unix :: proc(t: ^testing.T) { + Expected_Output :: `Usage: + varg required-number [number] [name] --bars --bots --foos --gadgets --variadic-flag --widgets [--array] [--count] [--greek] [--verbose] ... +Flags: + --required-number , required | some number + --number | some other number + --name + Multi-line documentation + gets formatted + very nicely. + --bars , exactly 3 | + --bots , at least 1 | + --foos , between 2 and 3 | + --gadgets , at least 1 | + --variadic-flag , at least 2 | + --widgets , at most 2 | + | + --array , multiple | + --count | + --greek | + --verbose | + | +` + + Custom_Enum :: enum { + Alpha, + Omega, + } + + S :: struct { + required_number: int `args:"pos=0,required" usage:"some number"`, + number: int `args:"pos=1" usage:"some other number"`, + name: string `args:"pos=2" usage:" + Multi-line documentation + gets formatted +very nicely. + +"`, + + c: u8 `args:"name=count"`, + greek: Custom_Enum, + + array: [dynamic]rune, + variadic_flag: [dynamic]int `args:"variadic,required=2"`, + + gadgets: [dynamic]string `args:"required=1"`, + widgets: [dynamic]string `args:"required=<3"`, + foos: [dynamic]string `args:"required=2<4"`, + bars: [dynamic]string `args:"required=3<4"`, + bots: [dynamic]string `args:"required"`, + + debug: bool `args:"hidden" usage:"print debug info"`, + verbose: bool, + + varg: [dynamic]string, + } + + builder := strings.builder_make() + defer strings.builder_destroy(&builder) + writer := strings.to_stream(&builder) + flags.write_usage(writer, S, "varg", .Unix) + testing.expect_value(t, strings.to_string(builder), Expected_Output) +} diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin index 4459af609..3a1eb37e7 100644 --- a/tests/core/fmt/test_core_fmt.odin +++ b/tests/core/fmt/test_core_fmt.odin @@ -1,47 +1,13 @@ package test_core_fmt +import "base:runtime" import "core:fmt" -import "core:os" -import "core:testing" +import "core:math" import "core:mem" +import "core:testing" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_fmt_memory(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - +@(test) test_fmt_memory :: proc(t: ^testing.T) { - check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) { - got := fmt.tprintf(format, ..args) - expect(t, got == exp, fmt.tprintf("(%q, %v): %q != %q", format, args, got, exp), loc) - } - check(t, "5b", "%m", 5) check(t, "5B", "%M", 5) check(t, "-5B", "%M", -5) @@ -52,8 +18,378 @@ test_fmt_memory :: proc(t: ^testing.T) { check(t, "3.50 gib", "%#m", u32(mem.Gigabyte * 3.5)) check(t, "01tib", "%5.0m", mem.Terabyte) check(t, "-1tib", "%5.0m", -mem.Terabyte) - check(t, "2.50 pib", "%#5.m", uint(mem.Petabyte * 2.5)) + check(t, "2 pib", "%#5.m", uint(mem.Petabyte * 2.5)) check(t, "1.00 EiB", "%#M", mem.Exabyte) check(t, "255 B", "%#M", u8(255)) check(t, "0b", "%m", u8(0)) } + +@(test) +test_fmt_complex_quaternion :: proc(t: ^testing.T) { + neg_inf := math.inf_f64(-1) + pos_inf := math.inf_f64(+1) + neg_zero := f64(0h80000000_00000000) + nan := math.nan_f64() + + // NOTE(Feoramund): Doing it this way, because complex construction is broken. + // Reported in issue #3665. + c: complex128 + cptr := cast(^runtime.Raw_Complex128)&c + + cptr^ = {0, 0} + check(t, "0+0i", "%v", c) + cptr^ = {1, 1} + check(t, "1+1i", "%v", c) + cptr^ = {1, 0} + check(t, "1+0i", "%v", c) + cptr^ = {-1, -1} + check(t, "-1-1i", "%v", c) + cptr^ = {0, neg_zero} + check(t, "0-0i", "%v", c) + cptr^ = {nan, nan} + check(t, "NaNNaNi", "%v", c) + cptr^ = {pos_inf, pos_inf} + check(t, "+Inf+Infi", "%v", c) + cptr^ = {neg_inf, neg_inf} + check(t, "-Inf-Infi", "%v", c) + + // Check forced plus signs. + cptr^ = {0, neg_zero} + check(t, "+0-0i", "%+v", c) + cptr^ = {1, 1} + check(t, "+1+1i", "%+v", c) + cptr^ = {nan, nan} + check(t, "NaNNaNi", "%+v", c) + cptr^ = {pos_inf, pos_inf} + check(t, "+Inf+Infi", "%+v", c) + cptr^ = {neg_inf, neg_inf} + check(t, "-Inf-Infi", "%+v", c) + + // Remember that the real number is the last in a quaternion's data layout, + // opposed to a complex, where it is the first. + q: quaternion256 + qptr := cast(^runtime.Raw_Quaternion256)&q + + qptr^ = {0, 0, 0, 0} + check(t, "0+0i+0j+0k", "%v", q) + qptr^ = {1, 1, 1, 1} + check(t, "1+1i+1j+1k", "%v", q) + qptr^ = {2, 3, 4, 1} + check(t, "1+2i+3j+4k", "%v", q) + qptr^ = {-1, -1, -1, -1} + check(t, "-1-1i-1j-1k", "%v", q) + qptr^ = {2, neg_zero, neg_zero, 1} + check(t, "1+2i-0j-0k", "%v", q) + qptr^ = {neg_inf, neg_inf, neg_inf, -1} + check(t, "-1-Infi-Infj-Infk", "%v", q) + qptr^ = {pos_inf, pos_inf, pos_inf, -1} + check(t, "-1+Infi+Infj+Infk", "%v", q) + qptr^ = {nan, nan, nan, -1} + check(t, "-1NaNiNaNjNaNk", "%v", q) +} + +@(test) +test_fmt_doc_examples :: proc(t: ^testing.T) { + // C-like syntax + check(t, "37 13", "%[1]d %[0]d", 13, 37) + check(t, "017.00", "%*[2].*[1][0]f", 17.0, 2, 6) + check(t, "017.00", "%6.2f", 17.0) + + // Python-like syntax + check(t, "37 13", "{1:d} {0:d}", 13, 37) + check(t, "017.00", "{0:*[2].*[1]f}", 17.0, 2, 6) + check(t, "017.00", "{:6.2f}", 17.0) +} + +@(test) +test_fmt_escaping_prefixes :: proc(t: ^testing.T) { + // Escaping + check(t, "% { } 0 { } } {", "%% {{ }} {} {{ }} }} {{", 0 ) + + // Prefixes + check(t, "+3.000", "%+f", 3.0 ) + check(t, "0003", "%04i", 3 ) + check(t, "3 ", "% -4i", 3 ) + check(t, "+3", "%+i", 3 ) + check(t, "0b11", "%#b", 3 ) + check(t, "0xA", "%#X", 10 ) +} + +@(test) +test_fmt_indexing :: proc(t: ^testing.T) { + // Specific index formatting + check(t, "1 2 3", "%i %i %i", 1, 2, 3) + check(t, "1 2 3", "%[0]i %[1]i %[2]i", 1, 2, 3) + check(t, "3 2 1", "%[2]i %[1]i %[0]i", 1, 2, 3) + check(t, "3 1 2", "%[2]i %i %i", 1, 2, 3) + check(t, "1 2 3", "%i %[1]i %i", 1, 2, 3) + check(t, "1 3 2", "%i %[2]i %i", 1, 2, 3) + check(t, "1 1 1", "%[0]i %[0]i %[0]i", 1) +} + +@(test) +test_fmt_width_precision :: proc(t: ^testing.T) { + // Width + check(t, "3.140", "%f", 3.14) + check(t, "3.140", "%4f", 3.14) + check(t, "3.140", "%5f", 3.14) + check(t, "03.140", "%6f", 3.14) + + // Precision + check(t, "3", "%.f", 3.14) + check(t, "3", "%.0f", 3.14) + check(t, "3.1", "%.1f", 3.14) + check(t, "3.140", "%.3f", 3.14) + check(t, "3.14000", "%.5f", 3.14) + + check(t, "3.1415", "%g", 3.1415) + + // Scientific notation + check(t, "3.000000e+00", "%e", 3.0) + + check(t, "3e+02", "%.e", 300.0) + check(t, "3e+02", "%.0e", 300.0) + check(t, "3.0e+02", "%.1e", 300.0) + check(t, "3.00e+02", "%.2e", 300.0) + check(t, "3.000e+02", "%.3e", 300.0) + + check(t, "3e+01", "%.e", 30.56) + check(t, "3e+01", "%.0e", 30.56) + check(t, "3.1e+01", "%.1e", 30.56) + check(t, "3.06e+01", "%.2e", 30.56) + check(t, "3.056e+01", "%.3e", 30.56) + + // Width and precision + check(t, "3.140", "%5.3f", 3.14) + check(t, "3.140", "%*[1].3f", 3.14, 5) + check(t, "3.140", "%*[1].*[2]f", 3.14, 5, 3) + check(t, "3.140", "%*[1].*[2][0]f", 3.14, 5, 3) + check(t, "3.140", "%*[2].*[1]f", 3.14, 3, 5) + check(t, "3.140", "%5.*[1]f", 3.14, 3) +} + +@(test) +test_fmt_arg_errors :: proc(t: ^testing.T) { + // Error checking + check(t, "%!(MISSING ARGUMENT)%!(NO VERB)", "%" ) + + check(t, "1%!(EXTRA 2, 3)", "%i", 1, 2, 3) + check(t, "2%!(EXTRA 1, 3)", "%[1]i", 1, 2, 3) + + check(t, "%!(BAD ARGUMENT NUMBER)%!(EXTRA 0)", "%[1]i", 0) + + check(t, "%!(MISSING ARGUMENT)", "%f") + check(t, "%!(BAD ARGUMENT NUMBER)%!(NO VERB)", "%[0]") + check(t, "%!(BAD ARGUMENT NUMBER)", "%[0]f") + + check(t, "%!(BAD ARGUMENT NUMBER)%!(NO VERB) %!(MISSING ARGUMENT)", "%[0] %i") + + check(t, "%!(NO VERB) 1%!(EXTRA 2)", "%[0] %i", 1, 2) + + check(t, "1 2 %!(MISSING ARGUMENT)", "%i %i %i", 1, 2) + check(t, "1 2 %!(BAD ARGUMENT NUMBER)", "%i %i %[2]i", 1, 2) + + check(t, "%!(BAD ARGUMENT NUMBER)%!(NO VERB)%!(EXTRA 0)", "%[1]", 0) + + check(t, "3.1%!(EXTRA 3.14)", "%.1f", 3.14, 3.14) +} + +@(test) +test_fmt_python_syntax :: proc(t: ^testing.T) { + // Python-like syntax + check(t, "1 2 3", "{} {} {}", 1, 2, 3) + check(t, "3 2 1", "{2} {1} {0}", 1, 2, 3) + check(t, "1 2 3", "{:i} {:i} {:i}", 1, 2, 3) + check(t, "1 2 3", "{0:i} {1:i} {2:i}", 1, 2, 3) + check(t, "3 2 1", "{2:i} {1:i} {0:i}", 1, 2, 3) + check(t, "3 1 2", "{2:i} {0:i} {1:i}", 1, 2, 3) + check(t, "1 2 3", "{:i} {1:i} {:i}", 1, 2, 3) + check(t, "1 3 2", "{:i} {2:i} {:i}", 1, 2, 3) + check(t, "1 1 1", "{0:i} {0:i} {0:i}", 1) + + check(t, "1 1%!(EXTRA 2)", "{} {0}", 1, 2) + check(t, "2 1", "{1} {}", 1, 2) + check(t, "%!(BAD ARGUMENT NUMBER) 1%!(EXTRA 2)", "{2} {}", 1, 2) + + check(t, "%!(BAD ARGUMENT NUMBER)", "{1}") + check(t, "%!(BAD ARGUMENT NUMBER)%!(NO VERB)", "{1:}") + check(t, "%!(BAD ARGUMENT NUMBER)%!(NO VERB)%!(EXTRA 0)", "{1:}", 0) + + check(t, "%!(MISSING ARGUMENT)", "{}" ) + check(t, "%!(MISSING ARGUMENT)%!(MISSING CLOSE BRACE)", "{" ) + check(t, "%!(MISSING CLOSE BRACE)%!(EXTRA 1)", "{", 1) + check(t, "%!(MISSING CLOSE BRACE)%!(EXTRA 1)", "{0", 1 ) +} + +@(test) +test_pointers :: proc(t: ^testing.T) { + S :: struct { i: int } + a: rawptr + b: ^int + c: ^S + d: ^S = cast(^S)cast(uintptr)0xFFFF + + check(t, "0x0", "%p", a) + check(t, "0x0", "%p", b) + check(t, "0x0", "%p", c) + check(t, "0xFFFF", "%p", d) + + check(t, "0x0", "%#p", a) + check(t, "0x0", "%#p", b) + check(t, "0x0", "%#p", c) + check(t, "0xFFFF", "%#p", d) + + check(t, "0x0", "%v", a) + check(t, "0x0", "%v", b) + check(t, "", "%v", c) + + check(t, "0x0", "%#v", a) + check(t, "0x0", "%#v", b) + check(t, "", "%#v", c) + + check(t, "0x0000", "%4p", a) + check(t, "0x0000", "%4p", b) + check(t, "0x0000", "%4p", c) + check(t, "0xFFFF", "%4p", d) + + check(t, "0x0000", "%#4p", a) + check(t, "0x0000", "%#4p", b) + check(t, "0x0000", "%#4p", c) + check(t, "0xFFFF", "%#4p", d) +} + +@(test) +test_odin_value_export :: proc(t: ^testing.T) { + E :: enum u32 { + A, B, C, + } + + F :: enum i16 { + A, B, F, + } + + S :: struct { + j, k: int, + } + + ST :: struct { + x: int `fmt:"-"`, + y: u8 `fmt:"r,0"`, + z: string `fmt:"s,0"`, + } + + U :: union { + i8, + i16, + } + + UEF :: union { E, F } + + A :: [2]int + + BSE :: distinct bit_set[E] + + i : int = 64 + f : f64 = 3.14 + c : complex128 = 7+3i + q : quaternion256 = 1+2i+3j+4k + mat : matrix[2,3]f32 = {1.5, 2, 1, 0.777, 0.333, 0.8} + matc : #column_major matrix[2,3]f32 = {1.5, 2, 1, 0.777, 0.333, 0.8} + e : enum {A, B, C} = .B + en : E = E.C + ena : [2]E = {E.A, E.C} + s : struct { j: int, k: int } = { j = 16, k = 8 } + sn : S = S{ j = 24, k = 12 } + st : ST = { 32768, 57, "Hellope" } + str : string = "Hellope" + strc : cstring = "Hellope" + bsu : bit_set[0..<32; u32] = {0, 1} + bs : bit_set[4..<16] = {5, 7} + bse : bit_set[E] = { .B, .A } + bsE : BSE = { .A, .C } + arr : [3]int = {1, 2, 3} + ars : [3]S = {S{j = 3, k = 2}, S{j = 2, k = 1}, S{j = 1, k = 0}} + darr : [dynamic]u8 = { 128, 64, 32 } + dars : [dynamic]S = {S{j = 1, k = 2}, S{j = 3, k = 4}} + na : A = {7, 5} + may0 : Maybe(int) + may1 : Maybe(int) = 1 + uz : union {i8, i16} = i8(-33) + u0 : U = U(nil) + u1 : U = i16(42) + uef0 : UEF = E.A + uefa : [3]UEF = { E.A, F.A, F.F } + map_ : map[string]u8 = {"foo" = 8, "bar" = 4} + + bf : bit_field int { + a: int | 4, + b: int | 4, + e: E | 4, + } = {a = 1, b = 2, e = .A} + + defer { + delete(darr) + delete(dars) + delete(map_) + } + + check(t, "64", "%w", i) + check(t, "3.14", "%w", f) + check(t, "7+3i", "%w", c) + check(t, "1+2i+3j+4k", "%w", q) + check(t, "{1.5, 2, 1, 0.777, 0.333, 0.8}", "%w", mat) + check(t, "{1.5, 2, 1, 0.777, 0.333, 0.8}", "%w", matc) + check(t, ".B", "%w", e) + check(t, "E.C", "%w", en) + check(t, "{E.A, E.C}", "%w", ena) + check(t, "{j = 16, k = 8}", "%w", s) + check(t, "S{j = 24, k = 12}", "%w", sn) + check(t, `ST{y = 57, z = "Hellope"}`, "%w", st) + check(t, `"Hellope"`, "%w", str) + check(t, `"Hellope"`, "%w", strc) + check(t, "{0, 1}", "%w", bsu) + check(t, "{5, 7}", "%w", bs) + check(t, "{E.A, E.B}", "%w", bse) + check(t, "{E.A, E.C}", "%w", bsE) + check(t, "{1, 2, 3}", "%w", arr) + check(t, "{S{j = 3, k = 2}, S{j = 2, k = 1}, S{j = 1, k = 0}}", "%w", ars) + check(t, "{128, 64, 32}", "%w", darr) + check(t, "{S{j = 1, k = 2}, S{j = 3, k = 4}}", "%w", dars) + check(t, "{7, 5}", "%w", na) + check(t, "nil", "%w", may0) + check(t, "1", "%w", may1) + check(t, "-33", "%w", uz) + check(t, "nil", "%w", u0) + check(t, "42", "%w", u1) + check(t, "E.A", "%w", uef0) + check(t, "{E.A, F.A, F.F}", "%w", uefa) + check(t, "{a = 1, b = 2, e = E.A}", "%w", bf) + // Check this manually due to the non-deterministic ordering of map keys. + switch fmt.tprintf("%w", map_) { + case `{"foo"=8, "bar"=4}`: break + case `{"bar"=4, "foo"=8}`: break + case: testing.fail(t) + } +} + +@(test) +leaking_struct_tag :: proc(t: ^testing.T) { + My_Struct :: struct { + names: [^]string `fmt:"v,name_count"`, + name_count: int, + } + + name := "hello?" + foo := My_Struct { + names = &name, + name_count = 1, + } + + check(t, "My_Struct{names = [\"hello?\"], name_count = 1}", "%v", foo) +} + +@(private) +check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) { + got := fmt.tprintf(format, ..args) + testing.expectf(t, got == exp, "(%q, %v): %q != %q", format, args, got, exp, loc = loc) +} diff --git a/tests/core/hash/test_core_hash.odin b/tests/core/hash/test_core_hash.odin index 932d2f34c..c3f0bee91 100644 --- a/tests/core/hash/test_core_hash.odin +++ b/tests/core/hash/test_core_hash.odin @@ -2,201 +2,12 @@ package test_core_hash import "core:hash/xxhash" import "core:hash" -import "core:time" import "core:testing" -import "core:fmt" -import "core:os" import "core:math/rand" import "base:intrinsics" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_benchmark_runner(&t) - test_crc64_vectors(&t) - test_xxhash_vectors(&t) - test_xxhash_large(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -/* - Benchmarks -*/ - -setup_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - assert(options != nil) - - options.input = make([]u8, options.bytes, allocator) - return nil if len(options.input) == options.bytes else .Allocation_Error -} - -teardown_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - assert(options != nil) - - delete(options.input) - return nil -} - -benchmark_xxh32 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - buf := options.input - - h: u32 - for _ in 0..=options.rounds { - h = xxhash.XXH32(buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - options.hash = u128(h) - return nil -} - -benchmark_xxh64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - buf := options.input - - h: u64 - for _ in 0..=options.rounds { - h = xxhash.XXH64(buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - options.hash = u128(h) - return nil -} - -benchmark_xxh3_64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - buf := options.input - - h: u64 - for _ in 0..=options.rounds { - h = xxhash.XXH3_64(buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - options.hash = u128(h) - return nil -} - -benchmark_xxh3_128 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - buf := options.input - - h: u128 - for _ in 0..=options.rounds { - h = xxhash.XXH3_128(buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - options.hash = h - return nil -} - -benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) { - fmt.printf("\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", - name, - options.rounds, - options.processed, - time.duration_nanoseconds(options.duration), - options.rounds_per_second, - options.megabytes_per_second, - ) -} - @test -test_benchmark_runner :: proc(t: ^testing.T) { - fmt.println("Starting benchmarks:") - - name := "XXH32 100 zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 100, - setup = setup_xxhash, - bench = benchmark_xxh32, - teardown = teardown_xxhash, - } - - err := time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x85f6413c, name) - benchmark_print(name, options) - - name = "XXH32 1 MiB zero bytes" - options.bytes = 1_048_576 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x9430f97f, name) - benchmark_print(name, options) - - name = "XXH64 100 zero bytes" - options.bytes = 100 - options.bench = benchmark_xxh64 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x17bb1103c92c502f, name) - benchmark_print(name, options) - - name = "XXH64 1 MiB zero bytes" - options.bytes = 1_048_576 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x87d2a1b6e1163ef1, name) - benchmark_print(name, options) - - name = "XXH3_64 100 zero bytes" - options.bytes = 100 - options.bench = benchmark_xxh3_64 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x801fedc74ccd608c, name) - benchmark_print(name, options) - - name = "XXH3_64 1 MiB zero bytes" - options.bytes = 1_048_576 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x918780b90550bf34, name) - benchmark_print(name, options) - - name = "XXH3_128 100 zero bytes" - options.bytes = 100 - options.bench = benchmark_xxh3_128 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x6ba30a4e9dffe1ff801fedc74ccd608c, name) - benchmark_print(name, options) - - name = "XXH3_128 1 MiB zero bytes" - options.bytes = 1_048_576 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0xb6ef17a3448492b6918780b90550bf34, name) - benchmark_print(name, options) -} - -@test -test_xxhash_large :: proc(t: ^testing.T) { +test_xxhash_zero_fixed :: proc(t: ^testing.T) { many_zeroes := make([]u8, 16 * 1024 * 1024) defer delete(many_zeroes) @@ -204,64 +15,47 @@ test_xxhash_large :: proc(t: ^testing.T) { for i, v in ZERO_VECTORS { b := many_zeroes[:i] - fmt.printf("[test_xxhash_large] All at once. Size: %v\n", i) - xxh32 := xxhash.XXH32(b) xxh64 := xxhash.XXH64(b) xxh3_64 := xxhash.XXH3_64(b) xxh3_128 := xxhash.XXH3_128(b) - xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x. Got: %08x.", i, v.xxh_32, xxh32) - xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh_64, xxh64) - xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64) - xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128) - - expect(t, xxh32 == v.xxh_32, xxh32_error) - expect(t, xxh64 == v.xxh_64, xxh64_error) - expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) - expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) + testing.expectf(t, xxh32 == v.xxh_32, "[ XXH32(%03d) ] Expected: %08x, got: %08x", i, v.xxh_32, xxh32) + testing.expectf(t, xxh64 == v.xxh_64, "[ XXH64(%03d) ] Expected: %16x, got: %16x", i, v.xxh_64, xxh64) + testing.expectf(t, xxh3_64 == v.xxh3_64, "[XXH3_64(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64) + testing.expectf(t, xxh3_128 == v.xxh3_128, "[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128) } +} - when #config(RAND_STATE, -1) >= 0 && #config(RAND_INC, -1) >= 0 { - random_seed := rand.Rand{ - state = u64(#config(RAND_STATE, -1)), - inc = u64(#config(RAND_INC, -1)), - } - fmt.printf("Using user-selected seed {{%v,%v}} for update size randomness.\n", random_seed.state, random_seed.inc) - } else { - random_seed := rand.create(u64(intrinsics.read_cycle_counter())) - fmt.printf("Randonly selected seed {{%v,%v}} for update size randomness.\n", random_seed.state, random_seed.inc) - } +@(test) +test_xxhash_zero_streamed_random_updates :: proc(t: ^testing.T) { + many_zeroes := make([]u8, 16 * 1024 * 1024) + defer delete(many_zeroes) // Streamed for i, v in ZERO_VECTORS { b := many_zeroes[:i] - fmt.printf("[test_xxhash_large] Streamed. Size: %v\n", i) - - // bytes_per_update := []int{1, 42, 13, 7, 16, 5, 23, 74, 1024, 511, 1023, 47} - // update_size_idx: int - xxh_32_state, xxh_32_err := xxhash.XXH32_create_state() defer xxhash.XXH32_destroy_state(xxh_32_state) - expect(t, xxh_32_err == nil, "Problem initializing XXH_32 state.") + testing.expect(t, xxh_32_err == nil, "Problem initializing XXH_32 state") xxh_64_state, xxh_64_err := xxhash.XXH64_create_state() defer xxhash.XXH64_destroy_state(xxh_64_state) - expect(t, xxh_64_err == nil, "Problem initializing XXH_64 state.") + testing.expect(t, xxh_64_err == nil, "Problem initializing XXH_64 state") xxh3_64_state, xxh3_64_err := xxhash.XXH3_create_state() defer xxhash.XXH3_destroy_state(xxh3_64_state) - expect(t, xxh3_64_err == nil, "Problem initializing XXH3_64 state.") + testing.expect(t, xxh3_64_err == nil, "Problem initializing XXH3_64 state") xxh3_128_state, xxh3_128_err := xxhash.XXH3_create_state() defer xxhash.XXH3_destroy_state(xxh3_128_state) - expect(t, xxh3_128_err == nil, "Problem initializing XXH3_128 state.") + testing.expect(t, xxh3_128_err == nil, "Problem initializing XXH3_128 state") // XXH3_128_update - + rand.reset(t.seed) for len(b) > 0 { - update_size := min(len(b), rand.int_max(8192, &random_seed)) + update_size := min(len(b), rand.int_max(8192)) if update_size > 4096 { update_size %= 73 } @@ -281,28 +75,19 @@ test_xxhash_large :: proc(t: ^testing.T) { xxh3_64 := xxhash.XXH3_64_digest(xxh3_64_state) xxh3_128 := xxhash.XXH3_128_digest(xxh3_128_state) - xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x. Got: %08x.", i, v.xxh_32, xxh32) - xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh_64, xxh64) - xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64) - xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128) - - expect(t, xxh32 == v.xxh_32, xxh32_error) - expect(t, xxh64 == v.xxh_64, xxh64_error) - expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) - expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) + testing.expectf(t, xxh32 == v.xxh_32, "[ XXH32(%03d) ] Expected: %08x, got: %08x", i, v.xxh_32, xxh32) + testing.expectf(t, xxh64 == v.xxh_64, "[ XXH64(%03d) ] Expected: %16x, got: %16x", i, v.xxh_64, xxh64) + testing.expectf(t, xxh3_64 == v.xxh3_64, "[XXH3_64(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64) + testing.expectf(t, xxh3_128 == v.xxh3_128, "[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128) } } @test -test_xxhash_vectors :: proc(t: ^testing.T) { - fmt.println("Verifying against XXHASH_TEST_VECTOR_SEEDED:") - +test_xxhash_seeded :: proc(t: ^testing.T) { buf := make([]u8, 256) defer delete(buf) for seed, table in XXHASH_TEST_VECTOR_SEEDED { - fmt.printf("\tSeed: %v\n", seed) - for v, i in table { b := buf[:i] @@ -311,60 +96,48 @@ test_xxhash_vectors :: proc(t: ^testing.T) { xxh3_64 := xxhash.XXH3_64(b, seed) xxh3_128 := xxhash.XXH3_128(b, seed) - xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x. Got: %08x.", i, v.xxh_32, xxh32) - xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh_64, xxh64) - - xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64) - xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128) - - expect(t, xxh32 == v.xxh_32, xxh32_error) - expect(t, xxh64 == v.xxh_64, xxh64_error) - expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) - expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) + testing.expectf(t, xxh32 == v.xxh_32, "[ XXH32(%03d) ] Expected: %08x, got: %08x", i, v.xxh_32, xxh32) + testing.expectf(t, xxh64 == v.xxh_64, "[ XXH64(%03d) ] Expected: %16x, got: %16x", i, v.xxh_64, xxh64) + testing.expectf(t, xxh3_64 == v.xxh3_64, "[XXH3_64(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64) + testing.expectf(t, xxh3_128 == v.xxh3_128, "[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128) if len(b) > xxhash.XXH3_MIDSIZE_MAX { - fmt.printf("XXH3 - size: %v\n", len(b)) - xxh3_state, _ := xxhash.XXH3_create_state() xxhash.XXH3_64_reset_with_seed(xxh3_state, seed) xxhash.XXH3_64_update(xxh3_state, b) xxh3_64_streamed := xxhash.XXH3_64_digest(xxh3_state) xxhash.XXH3_destroy_state(xxh3_state) - xxh3_64s_error := fmt.tprintf("[XXH3_64s(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64_streamed) - expect(t, xxh3_64_streamed == v.xxh3_64, xxh3_64s_error) + testing.expectf(t, xxh3_64_streamed == v.xxh3_64, "[XXH3_64s(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64_streamed) xxh3_state2, _ := xxhash.XXH3_create_state() xxhash.XXH3_128_reset_with_seed(xxh3_state2, seed) xxhash.XXH3_128_update(xxh3_state2, b) xxh3_128_streamed := xxhash.XXH3_128_digest(xxh3_state2) xxhash.XXH3_destroy_state(xxh3_state2) - xxh3_128s_error := fmt.tprintf("[XXH3_128s(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128_streamed) - expect(t, xxh3_128_streamed == v.xxh3_128, xxh3_128s_error) + testing.expectf(t, xxh3_128_streamed == v.xxh3_128, "[XXH3_128s(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128_streamed) } } } +} + +@test +test_xxhash_secret :: proc(t: ^testing.T) { + buf := make([]u8, 256) + defer delete(buf) - fmt.println("Verifying against XXHASH_TEST_VECTOR_SECRET:") for secret, table in XXHASH_TEST_VECTOR_SECRET { - fmt.printf("\tSecret:\n\t\t\"%v\"\n", secret) - secret_bytes := transmute([]u8)secret - for v, i in table { b := buf[:i] xxh3_128 := xxhash.XXH3_128(b, secret_bytes) - xxh3_128_error := fmt.tprintf("[XXH3_128(%03d)] Expected: %32x. Got: %32x.", i, v.xxh3_128_secret, xxh3_128) - - expect(t, xxh3_128 == v.xxh3_128_secret, xxh3_128_error) + testing.expectf(t, xxh3_128 == v.xxh3_128_secret, "[XXH3_128(%03d)] Expected: %32x, got: %32x", i, v.xxh3_128_secret, xxh3_128) } } } @test test_crc64_vectors :: proc(t: ^testing.T) { - fmt.println("Verifying CRC-64:") - vectors := map[string][4]u64 { "123456789" = { 0x6c40df5f0b497347, // ECMA-182, @@ -379,23 +152,18 @@ test_crc64_vectors :: proc(t: ^testing.T) { 0xe7fcf1006b503b61, // ISO 3306, input and output inverted }, } + defer delete(vectors) for vector, expected in vectors { - fmt.println("\tVector:", vector) b := transmute([]u8)vector ecma := hash.crc64_ecma_182(b) xz := hash.crc64_xz(b) iso := hash.crc64_iso_3306(b) iso2 := hash.crc64_iso_3306_inverse(b) - ecma_error := fmt.tprintf("[ CRC-64 ECMA ] Expected: %016x. Got: %016x.", expected[0], ecma) - xz_error := fmt.tprintf("[ CRC-64 XZ ] Expected: %016x. Got: %016x.", expected[1], xz) - iso_error := fmt.tprintf("[ CRC-64 ISO 3306] Expected: %016x. Got: %016x.", expected[2], iso) - iso2_error := fmt.tprintf("[~CRC-64 ISO 3306] Expected: %016x. Got: %016x.", expected[3], iso2) - - expect(t, ecma == expected[0], ecma_error) - expect(t, xz == expected[1], xz_error) - expect(t, iso == expected[2], iso_error) - expect(t, iso2 == expected[3], iso2_error) + testing.expectf(t, ecma == expected[0], "[ CRC-64 ECMA ] Expected: %016x, got: %016x", expected[0], ecma) + testing.expectf(t, xz == expected[1], "[ CRC-64 XZ ] Expected: %016x, got: %016x", expected[1], xz) + testing.expectf(t, iso == expected[2], "[ CRC-64 ISO 3306] Expected: %016x, got: %016x", expected[2], iso) + testing.expectf(t, iso2 == expected[3], "[~CRC-64 ISO 3306] Expected: %016x, got: %016x", expected[3], iso2) } } \ No newline at end of file diff --git a/tests/core/hash/test_vectors_xxhash.odin b/tests/core/hash/test_vectors_xxhash.odin index 6a37aef30..f72e2699a 100644 --- a/tests/core/hash/test_vectors_xxhash.odin +++ b/tests/core/hash/test_vectors_xxhash.odin @@ -1,6 +1,4 @@ -/* - Hash Test Vectors -*/ +// Hash Test Vectors package test_core_hash XXHASH_Test_Vectors :: struct #packed { @@ -6789,4 +6787,4 @@ XXHASH_TEST_VECTOR_SECRET := map[string][257]XXHASH_Test_Vectors_With_Secret{ /* XXH3_128_with_secret */ 0x0f9b41191242ade48bbde48dff0d38ec, }, }, -} +} \ No newline at end of file diff --git a/tests/core/image/build.bat b/tests/core/image/build.bat deleted file mode 100644 index 03ee6b9a5..000000000 --- a/tests/core/image/build.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -pushd .. -odin run image -popd \ No newline at end of file diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index ae92ca617..899596229 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -1,11 +1,11 @@ /* - Copyright 2021 Jeroen van Rijn . + Copyright 2021-2024 Jeroen van Rijn . Made available under Odin's BSD-3 license. List of contributors: Jeroen van Rijn: Initial implementation. - A test suite for PNG + QOI. + A test suite for PNG, TGA, NetPBM, QOI and BMP. */ package test_core_image @@ -13,6 +13,7 @@ import "core:testing" import "core:compress" import "core:image" +import "core:image/bmp" import pbm "core:image/netpbm" import "core:image/png" import "core:image/qoi" @@ -20,59 +21,24 @@ import "core:image/tga" import "core:bytes" import "core:hash" -import "core:fmt" import "core:strings" - import "core:mem" -import "core:os" import "core:time" -import "base:runtime" +TEST_SUITE_PATH_PNG :: ODIN_ROOT + "tests/core/assets/PNG" +TEST_SUITE_PATH_BMP :: ODIN_ROOT + "tests/core/assets/BMP" -TEST_SUITE_PATH :: "assets/PNG" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} I_Error :: image.Error -main :: proc() { - t := testing.T{} - png_test(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -PNG_Test :: struct { +Test :: struct { file: string, tests: []struct { options: image.Options, expected_error: image.Error, - dims: PNG_Dims, + dims: Dims, hash: u32, }, } - Default :: image.Options{} Alpha_Add :: image.Options{.alpha_add_if_missing} Premul_Drop :: image.Options{.alpha_premultiply, .alpha_drop_if_present} @@ -82,19 +48,18 @@ Blend_BG_Keep :: image.Options{.blend_background, .alpha_add_if_missing} Return_Metadata :: image.Options{.return_metadata} No_Channel_Expansion :: image.Options{.do_not_expand_channels, .return_metadata} -PNG_Dims :: struct { +Dims :: struct { width: int, height: int, channels: int, depth: int, } -Basic_PNG_Tests := []PNG_Test{ +Basic_PNG_Tests := []Test{ /* Basic format tests: http://www.schaik.com/pngsuite/pngsuite_bas_png.html */ - { "basn0g01", // Black and white. { @@ -202,7 +167,7 @@ Basic_PNG_Tests := []PNG_Test{ }, } -Interlaced_PNG_Tests := []PNG_Test{ +Interlaced_PNG_Tests := []Test{ /* Interlaced format tests: http://www.schaik.com/pngsuite/pngsuite_int_png.html @@ -320,9 +285,9 @@ Interlaced_PNG_Tests := []PNG_Test{ }, } -Odd_Sized_PNG_Tests := []PNG_Test{ +Odd_Sized_PNG_Tests := []Test{ /* -" PngSuite", // Odd sizes / PNG-files: + "PngSuite", // Odd sizes / PNG-files: http://www.schaik.com/pngsuite/pngsuite_siz_png.html This tests curious sizes with and without interlacing. @@ -546,7 +511,7 @@ Odd_Sized_PNG_Tests := []PNG_Test{ }, } -PNG_bKGD_Tests := []PNG_Test{ +PNG_bKGD_Tests := []Test{ /* " PngSuite", // Background colors / PNG-files: http://www.schaik.com/pngsuite/pngsuite_bck_png.html @@ -633,7 +598,7 @@ PNG_bKGD_Tests := []PNG_Test{ }, } -PNG_tRNS_Tests := []PNG_Test{ +PNG_tRNS_Tests := []Test{ /* PngSuite - Transparency: http://www.schaik.com/pngsuite/pngsuite_trn_png.html @@ -795,7 +760,7 @@ PNG_tRNS_Tests := []PNG_Test{ }, } -PNG_Filter_Tests := []PNG_Test{ +PNG_Filter_Tests := []Test{ /* PngSuite - Image filtering: @@ -874,7 +839,7 @@ PNG_Filter_Tests := []PNG_Test{ }, } -PNG_Varied_IDAT_Tests := []PNG_Test{ +PNG_Varied_IDAT_Tests := []Test{ /* PngSuite - Chunk ordering: @@ -933,7 +898,7 @@ PNG_Varied_IDAT_Tests := []PNG_Test{ }, } -PNG_ZLIB_Levels_Tests := []PNG_Test{ +PNG_ZLIB_Levels_Tests := []Test{ /* PngSuite - Zlib compression: @@ -974,7 +939,7 @@ PNG_ZLIB_Levels_Tests := []PNG_Test{ }, } -PNG_sPAL_Tests := []PNG_Test{ +PNG_sPAL_Tests := []Test{ /* PngSuite - Additional palettes: @@ -1021,7 +986,7 @@ PNG_sPAL_Tests := []PNG_Test{ }, } -PNG_Ancillary_Tests := []PNG_Test{ +PNG_Ancillary_Tests := []Test{ /* PngSuite" - Ancillary chunks: @@ -1189,7 +1154,7 @@ PNG_Ancillary_Tests := []PNG_Test{ } -Corrupt_PNG_Tests := []PNG_Test{ +Corrupt_PNG_Tests := []Test{ /* PngSuite - Corrupted files / PNG-files: @@ -1285,7 +1250,7 @@ Corrupt_PNG_Tests := []PNG_Test{ } -No_Postprocesing_Tests := []PNG_Test{ +No_Postprocesing_Tests := []Test{ /* These are some custom tests where we skip expanding to RGB(A). */ @@ -1309,8 +1274,6 @@ No_Postprocesing_Tests := []PNG_Test{ }, } - - Text_Title :: "PngSuite" Text_Software :: "Created on a NeXTstation color using \"pnmtopng\"." Text_Descrption :: "A compilation of a set of images created to test the\nvarious color-types of the PNG format. Included are\nblack&white, color, paletted, with alpha channel, with\ntransparency formats. All bit-depths allowed according\nto the spec are present." @@ -1430,82 +1393,92 @@ Expected_Text := map[string]map[string]png.Text { } @test -png_test :: proc(t: ^testing.T) { - - total_tests := 0 - total_expected := 235 - - PNG_Suites := [][]PNG_Test{ - Basic_PNG_Tests, - Interlaced_PNG_Tests, - Odd_Sized_PNG_Tests, - PNG_bKGD_Tests, - PNG_tRNS_Tests, - PNG_Filter_Tests, - PNG_Varied_IDAT_Tests, - PNG_ZLIB_Levels_Tests, - PNG_sPAL_Tests, - PNG_Ancillary_Tests, - Corrupt_PNG_Tests, - - No_Postprocesing_Tests, - - } - - for suite in PNG_Suites { - total_tests += run_png_suite(t, suite) - } - - error := fmt.tprintf("Expected %v PNG tests, %v ran.", total_expected, total_tests) - expect(t, total_tests == total_expected, error) +png_test_basic :: proc(t: ^testing.T) { + run_png_suite(t, Basic_PNG_Tests) } -run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { +@test +png_test_interlaced :: proc(t: ^testing.T) { + run_png_suite(t, Interlaced_PNG_Tests) +} - context = runtime.default_context() +@test +png_test_odd_sized :: proc(t: ^testing.T) { + run_png_suite(t, Odd_Sized_PNG_Tests) +} +@test +png_test_bKGD :: proc(t: ^testing.T) { + run_png_suite(t, PNG_bKGD_Tests) +} + +@test +png_test_tRNS :: proc(t: ^testing.T) { + run_png_suite(t, PNG_tRNS_Tests) +} + +@test +png_test_sPAL :: proc(t: ^testing.T) { + run_png_suite(t, PNG_sPAL_Tests) +} + +@test +png_test_filter :: proc(t: ^testing.T) { + run_png_suite(t, PNG_Filter_Tests) +} + +@test +png_test_varied_idat :: proc(t: ^testing.T) { + run_png_suite(t, PNG_Varied_IDAT_Tests) +} + +@test +png_test_zlib_levels :: proc(t: ^testing.T) { + run_png_suite(t, PNG_ZLIB_Levels_Tests) +} + +@test +png_test_ancillary :: proc(t: ^testing.T) { + run_png_suite(t, PNG_Ancillary_Tests) +} + +@test +png_test_corrupt :: proc(t: ^testing.T) { + run_png_suite(t, Corrupt_PNG_Tests) +} + +@test +png_test_no_postproc :: proc(t: ^testing.T) { + run_png_suite(t, No_Postprocesing_Tests) +} + +run_png_suite :: proc(t: ^testing.T, suite: []Test) { for file in suite { - test_file := strings.concatenate({TEST_SUITE_PATH, "/", file.file, ".png"}, context.temp_allocator) + test_file := strings.concatenate({TEST_SUITE_PATH_PNG, "/", file.file, ".png"}, context.allocator) + defer delete(test_file) img: ^png.Image err: png.Error count := 0 for test in file.tests { - count += 1 - subtotal += 1 - passed := false - - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) + count += 1 img, err = png.load(test_file, test.options) - error := fmt.tprintf("%v failed with %v.", test_file, err) - - passed = (test.expected_error == nil && err == nil) || (test.expected_error == err) - - expect(t, passed, error) + passed := (test.expected_error == nil && err == nil) || (test.expected_error == err) + testing.expectf(t, passed, "%q failed to load with error %v.", file.file, err) if err == nil { // No point in running the other tests if it didn't load. pixels := bytes.buffer_to_bytes(&img.pixels) - // This struct compare fails at -opt:2 if PNG_Dims is not #packed. - - dims := PNG_Dims{img.width, img.height, img.channels, img.depth} - error = fmt.tprintf("%v has %v, expected: %v.", file.file, dims, test.dims) - + dims := Dims{img.width, img.height, img.channels, img.depth} dims_pass := test.dims == dims - - expect(t, dims_pass, error) - + testing.expectf(t, dims_pass, "%v has %v, expected: %v", file.file, dims, test.dims) passed &= dims_pass - png_hash := hash.crc32(pixels) - error = fmt.tprintf("%v test %v hash is %08x, expected %08x with %v.", file.file, count, png_hash, test.hash, test.options) - expect(t, test.hash == png_hash, error) - + png_hash := hash.crc32(pixels) + testing.expectf(t, test.hash == png_hash, "%v test %v hash is %08x, expected %08x with %v", file.file, count, png_hash, test.hash, test.options) passed &= test.hash == png_hash if passed { @@ -1515,19 +1488,16 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { defer bytes.buffer_destroy(&qoi_buffer) qoi_save_err := qoi.save(&qoi_buffer, img) - error = fmt.tprintf("%v test %v QOI save failed with %v.", file.file, count, qoi_save_err) - expect(t, qoi_save_err == nil, error) + testing.expectf(t, qoi_save_err == nil, "%v test %v QOI save failed with %v", file.file, count, qoi_save_err) if qoi_save_err == nil { qoi_img, qoi_load_err := qoi.load(qoi_buffer.buf[:]) defer qoi.destroy(qoi_img) - error = fmt.tprintf("%v test %v QOI load failed with %v.", file.file, count, qoi_load_err) - expect(t, qoi_load_err == nil, error) + testing.expectf(t, qoi_load_err == nil, "%v test %v QOI load failed with %v", file.file, count, qoi_load_err) qoi_hash := hash.crc32(qoi_img.pixels.buf[:]) - error = fmt.tprintf("%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, qoi_hash, png_hash, test.options) - expect(t, qoi_hash == png_hash, error) + testing.expectf(t, qoi_hash == png_hash, "%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v", file.file, count, qoi_hash, png_hash, test.options) } } @@ -1537,19 +1507,15 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { defer bytes.buffer_destroy(&tga_buffer) tga_save_err := tga.save(&tga_buffer, img) - error = fmt.tprintf("%v test %v TGA save failed with %v.", file.file, count, tga_save_err) - expect(t, tga_save_err == nil, error) - + testing.expectf(t, tga_save_err == nil, "%v test %v TGA save failed with %v", file.file, count, tga_save_err) if tga_save_err == nil { tga_img, tga_load_err := tga.load(tga_buffer.buf[:]) defer tga.destroy(tga_img) - error = fmt.tprintf("%v test %v TGA load failed with %v.", file.file, count, tga_load_err) - expect(t, tga_load_err == nil, error) + testing.expectf(t, tga_load_err == nil, "%v test %v TGA load failed with %v", file.file, count, tga_load_err) tga_hash := hash.crc32(tga_img.pixels.buf[:]) - error = fmt.tprintf("%v test %v TGA load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, tga_hash, png_hash, test.options) - expect(t, tga_hash == png_hash, error) + testing.expectf(t, tga_hash == png_hash, "%v test %v TGA load hash is %08x, expected it match PNG's %08x with %v", file.file, count, tga_hash, png_hash, test.options) } } @@ -1558,22 +1524,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { pbm_buf, pbm_save_err := pbm.save_to_buffer(img) defer delete(pbm_buf) - error = fmt.tprintf("%v test %v PBM save failed with %v.", file.file, count, pbm_save_err) - expect(t, pbm_save_err == nil, error) + testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v", file.file, count, pbm_save_err) if pbm_save_err == nil { // Try to load it again. pbm_img, pbm_load_err := pbm.load(pbm_buf) defer pbm.destroy(pbm_img) - error = fmt.tprintf("%v test %v PBM load failed with %v.", file.file, count, pbm_load_err) - expect(t, pbm_load_err == nil, error) + testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v", file.file, count, pbm_load_err) if pbm_load_err == nil { pbm_hash := hash.crc32(pbm_img.pixels.buf[:]) - - error = fmt.tprintf("%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options) - expect(t, pbm_hash == png_hash, error) + testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v", file.file, count, pbm_hash, png_hash, test.options) } } } @@ -1587,22 +1549,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { pbm_buf, pbm_save_err := pbm.save_to_buffer(img, pbm_info) defer delete(pbm_buf) - error = fmt.tprintf("%v test %v PBM save failed with %v.", file.file, count, pbm_save_err) - expect(t, pbm_save_err == nil, error) + testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v", file.file, count, pbm_save_err) if pbm_save_err == nil { // Try to load it again. pbm_img, pbm_load_err := pbm.load(pbm_buf) defer pbm.destroy(pbm_img) - error = fmt.tprintf("%v test %v PBM load failed with %v.", file.file, count, pbm_load_err) - expect(t, pbm_load_err == nil, error) + testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v", file.file, count, pbm_load_err) if pbm_load_err == nil { pbm_hash := hash.crc32(pbm_img.pixels.buf[:]) - - error = fmt.tprintf("%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options) - expect(t, pbm_hash == png_hash, error) + testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v", file.file, count, pbm_hash, png_hash, test.options) } } } @@ -1655,21 +1613,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { float_pbm_buf, float_pbm_save_err := pbm.save_to_buffer(float_img, pbm_info) defer delete(float_pbm_buf) - error = fmt.tprintf("%v test %v save as PFM failed with %v", file.file, count, float_pbm_save_err) - expect(t, float_pbm_save_err == nil, error) + testing.expectf(t, float_pbm_save_err == nil, "%v test %v save as PFM failed with %v", file.file, count, float_pbm_save_err) if float_pbm_save_err == nil { // Load float image and compare. float_pbm_img, float_pbm_load_err := pbm.load(float_pbm_buf) defer pbm.destroy(float_pbm_img) - error = fmt.tprintf("%v test %v PFM load failed with %v", file.file, count, float_pbm_load_err) - expect(t, float_pbm_load_err == nil, error) + testing.expectf(t, float_pbm_load_err == nil, "%v test %v PFM load failed with %v", file.file, count, float_pbm_load_err) load_float := mem.slice_data_cast([]f32, float_pbm_img.pixels.buf[:]) - error = fmt.tprintf("%v test %v PFM load returned %v floats, expected %v", file.file, count, len(load_float), len(orig_float)) - expect(t, len(load_float) == len(orig_float), error) + testing.expectf(t, len(load_float) == len(orig_float), "%v test %v PFM load returned %v floats, expected %v", file.file, count, len(load_float), len(orig_float)) // Compare floats equal := true @@ -1679,15 +1634,13 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { break } } - error = fmt.tprintf("%v test %v PFM loaded floats to match", file.file, count) - expect(t, equal, error) + testing.expectf(t, equal, "%v test %v PFM loaded floats to match", file.file, count) } } } } if .return_metadata in test.options { - if v, ok := img.metadata.(^image.PNG_Info); ok { for c in v.chunks { #partial switch(c.header.type) { @@ -1696,8 +1649,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { case "pp0n2c16", "pp0n6a08": gamma, gamma_ok := png.gamma(c) expected_gamma := f32(1.0) - error = fmt.tprintf("%v test %v gAMA is %v, expected %v.", file.file, count, gamma, expected_gamma) - expect(t, gamma == expected_gamma && gamma_ok, error) + testing.expectf(t, gamma == expected_gamma && gamma_ok, "%v test %v gAMA is %v, expected %v", file.file, count, gamma, expected_gamma) } case .PLTE: switch(file.file) { @@ -1705,8 +1657,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { plte, plte_ok := png.plte(c) expected_plte_len := u16(216) - error = fmt.tprintf("%v test %v PLTE length is %v, expected %v.", file.file, count, plte.used, expected_plte_len) - expect(t, expected_plte_len == plte.used && plte_ok, error) + testing.expectf(t, expected_plte_len == plte.used && plte_ok, "%v test %v PLTE length is %v, expected %v", file.file, count, plte.used, expected_plte_len) } case .sPLT: switch(file.file) { @@ -1714,12 +1665,10 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { splt, splt_ok := png.splt(c) expected_splt_len := u16(216) - error = fmt.tprintf("%v test %v sPLT length is %v, expected %v.", file.file, count, splt.used, expected_splt_len) - expect(t, expected_splt_len == splt.used && splt_ok, error) + testing.expectf(t, expected_splt_len == splt.used && splt_ok, "%v test %v sPLT length is %v, expected %v", file.file, count, splt.used, expected_splt_len) expected_splt_name := "six-cube" - error = fmt.tprintf("%v test %v sPLT name is %v, expected %v.", file.file, count, splt.name, expected_splt_name) - expect(t, expected_splt_name == splt.name && splt_ok, error) + testing.expectf(t, expected_splt_name == splt.name && splt_ok, "%v test %v sPLT name is %v, expected %v", file.file, count, splt.name, expected_splt_name) png.splt_destroy(splt) } @@ -1733,48 +1682,37 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { g = png.CIE_1931{x = 0.3000, y = 0.6000}, b = png.CIE_1931{x = 0.1500, y = 0.0600}, } - error = fmt.tprintf("%v test %v cHRM is %v, expected %v.", file.file, count, chrm, expected_chrm) - expect(t, expected_chrm == chrm && chrm_ok, error) + testing.expectf(t, expected_chrm == chrm && chrm_ok, "%v test %v cHRM is %v, expected %v", file.file, count, chrm, expected_chrm) } case .pHYs: phys, phys_ok := png.phys(c) - phys_err := "%v test %v cHRM is %v, expected %v." switch (file.file) { case "cdfn2c08": expected_phys := png.pHYs{ppu_x = 1, ppu_y = 4, unit = .Unknown} - error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys && phys_ok, error) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys) case "cdhn2c08": expected_phys := png.pHYs{ppu_x = 4, ppu_y = 1, unit = .Unknown} - error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys && phys_ok, error) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys) case "cdsn2c08": expected_phys := png.pHYs{ppu_x = 1, ppu_y = 1, unit = .Unknown} - error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys && phys_ok, error) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys) case "cdun2c08": expected_phys := png.pHYs{ppu_x = 1000, ppu_y = 1000, unit = .Meter} - error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys && phys_ok, error) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys) } case .hIST: hist, hist_ok := png.hist(c) - hist_err := "%v test %v hIST has %v entries, expected %v." switch (file.file) { case "ch1n3p04": - error = fmt.tprintf(hist_err, file.file, count, hist.used, 15) - expect(t, hist.used == 15 && hist_ok, error) + testing.expectf(t, hist.used == 15 && hist_ok, "%v test %v hIST has %v entries, expected %v", file.file, count, hist.used, 15) case "ch2n3p08": - error = fmt.tprintf(hist_err, file.file, count, hist.used, 256) - expect(t, hist.used == 256 && hist_ok, error) + testing.expectf(t, hist.used == 256 && hist_ok, "%v test %v hIST has %v entries, expected %v", file.file, count, hist.used, 256) } case .tIME: png_time, png_time_ok := png.time(c) - time_err := "%v test %v tIME was %v, expected %v." expected_time: png.tIME core_time, core_time_ok := png.core_time(c) - time_core_err := "%v test %v tIME->core:time is %v, expected %v." expected_core: time.Time switch(file.file) { @@ -1789,14 +1727,10 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { expected_core = time.Time{_nsec = 946684799000000000} } - error = fmt.tprintf(time_err, file.file, count, png_time, expected_time) - expect(t, png_time == expected_time && png_time_ok, error) - - error = fmt.tprintf(time_core_err, file.file, count, core_time, expected_core) - expect(t, core_time == expected_core && core_time_ok, error) + testing.expectf(t, png_time == expected_time && png_time_ok, "%v test %v tIME was %v, expected %v", file.file, count, png_time, expected_time) + testing.expectf(t, core_time == expected_core && core_time_ok, "%v test %v tIME->core:time is %v, expected %v", file.file, count, core_time, expected_core) case .sBIT: sbit, sbit_ok := png.sbit(c) - sbit_err := "%v test %v sBIT was %v, expected %v." expected_sbit: [4]u8 switch (file.file) { @@ -1815,8 +1749,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { case "cdfn2c08", "cdhn2c08", "cdsn2c08", "cdun2c08", "ch1n3p04", "basn3p04": expected_sbit = [4]u8{ 4, 4, 4, 0} } - error = fmt.tprintf(sbit_err, file.file, count, sbit, expected_sbit) - expect(t, sbit == expected_sbit && sbit_ok, error) + testing.expectf(t, sbit == expected_sbit && sbit_ok, "%v test %v sBIT was %v, expected %v", file.file, count, sbit, expected_sbit) case .tEXt, .zTXt: text, text_ok := png.text(c) defer png.text_destroy(text) @@ -1828,8 +1761,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test_text := Expected_Text[file.file][text.keyword].text - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text.text, test_text) - expect(t, text.text == test_text && text_ok, error) + testing.expectf(t, text.text == test_text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text.text, test_text) } } } @@ -1842,74 +1774,623 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) } } case "ctfn0g04": // international UTF-8, finnish if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.text == test.text && text_ok, error) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) } } case "ctgn0g04": // international UTF-8, greek if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.text == test.text && text_ok, error) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) } } case "cthn0g04": // international UTF-8, hindi if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.text == test.text && text_ok, error) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) } } case "ctjn0g04": // international UTF-8, japanese if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.text == test.text && text_ok, error) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) } } } case .eXIf: if file.file == "exif2c08" { // chunk with jpeg exif data exif, exif_ok := png.exif(c) - error = fmt.tprintf("%v test %v eXIf byte order '%v', expected 'big_endian'.", file.file, count, exif.byte_order) - error_len := fmt.tprintf("%v test %v eXIf data length '%v', expected '%v'.", file.file, len(exif.data), 978) - expect(t, exif.byte_order == .big_endian && exif_ok, error) - expect(t, len(exif.data) == 978 && exif_ok, error_len) + testing.expectf(t, exif.byte_order == .big_endian && exif_ok, "%v test %v eXIf byte order '%v', expected 'big_endian'.", file.file, count, exif.byte_order) + testing.expectf(t, len(exif.data) == 978 && exif_ok, "%v test %v eXIf data length '%v', expected '%v'", file.file, len(exif.data), 978) } } } } } } - png.destroy(img) + } + } + return +} - for _, v in track.allocation_map { - error = fmt.tprintf("%v test %v leaked %v bytes @ loc %v.", file.file, count, v.size, v.location) - expect(t, false, error) +/* + Basic format tests: + https://entropymine.com/jason/bmpsuite/bmpsuite/html/bmpsuite.html - Version 2.8; 2023-11-28 + + The BMP Suite image generator itself is GPL, and isn't included, nor did it have its code referenced. + We do thank the author for the well-researched test suite, which we are free to include: + + "Image files generated by this program are not covered by this license, and are + in the public domain (except for the embedded ICC profiles)." + + The files with embedded ICC profiles aren't part of Odin's test assets. We don't support BMP metadata. + We don't support all "possibly correct" images, and thus only ship a subset of these from the BMP Suite. +*/ +Basic_BMP_Tests := []Test{ + { + "pal1", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "pal1wb", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "pal1bg", { + {Default, nil, {127, 64, 3, 8}, 0x_9e91_174a}, + }, + }, + { + "pal4", { + {Default, nil, {127, 64, 3, 8}, 0x_288e_4371}, + }, + }, + { + "pal4gs", { + {Default, nil, {127, 64, 3, 8}, 0x_452d_a01a}, + }, + }, + { + "pal4rle", { + {Default, nil, {127, 64, 3, 8}, 0x_288e_4371}, + }, + }, + { + "pal8", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8-0", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8gs", { + {Default, nil, {127, 64, 3, 8}, 0x_09c2_7834}, + }, + }, + { + "pal8rle", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8w126", { + {Default, nil, {126, 63, 3, 8}, 0x_bb66_4cda}, + }, + }, + { + "pal8w125", { + {Default, nil, {125, 62, 3, 8}, 0x_3ab8_f7c5}, + }, + }, + { + "pal8w124", { + {Default, nil, {124, 61, 3, 8}, 0x_b53e_e6c8}, + }, + }, + { + "pal8topdown", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8nonsquare", { + {Default, nil, {127, 32, 3, 8}, 0x_8409_c689}, + }, + }, + { + "pal8v4", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8v5", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "rgb16", { + {Default, nil, {127, 64, 3, 8}, 0x_8b6f_81a2}, + }, + }, + { + "rgb16bfdef", { + {Default, nil, {127, 64, 3, 8}, 0x_8b6f_81a2}, + }, + }, + { + "rgb16-565", { + {Default, nil, {127, 64, 3, 8}, 0x_8c73_a2ff}, + }, + }, + { + "rgb16-565pal", { + {Default, nil, {127, 64, 3, 8}, 0x_8c73_a2ff}, + }, + }, + { + "rgb24", { + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, + { + "rgb24pal", { + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, + { + "rgb32", { + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, + { + "rgb32bf", { + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, + { + "rgb32bfdef", { + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, +} + +OS2_Tests := []Test{ + { + "pal8os2", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8os2-sz", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8os2-hs", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8os2sp", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8os2v2", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8os2v2-16", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8os2v2-sz", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8os2v2-40sz", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, +} + +// BMP files that aren't 100% to spec. Some we support, some we don't. +Questionable_BMP_Tests := []Test{ + { + "pal1p1", { // Spec says 1-bit image has 2 palette entries. This one has 1. + {Default, nil, {127, 64, 3, 8}, 0x_2b54_2560}, + }, + }, + { + "pal2", { // 2-bit. Allowed on Windows CE. Irfanview doesn't support it. + {Default, nil, {127, 64, 3, 8}, 0x_0da2_7594}, + }, + }, + { + "pal2color", { // 2-bit, with color palette. + {Default, nil, {127, 64, 3, 8}, 0x_f0d8_c5d6}, + }, + }, + { + "pal8offs", { // 300 palette entries (yes, only 256 can be used) + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8oversizepal", { // Some padding between palette and image data + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal4rletrns", { // Using palette tricks to skip pixels + {Default, nil, {127, 64, 3, 8}, 0x_eed4_e744}, + }, + }, + { + "pal4rlecut", { // Using palette tricks to skip pixels + {Default, nil, {127, 64, 3, 8}, 0x_473fbc7d}, + }, + }, + { + "pal8rletrns", { // Using palette tricks to skip pixels + {Default, nil, {127, 64, 3, 8}, 0x_fe1f_e560}, + }, + }, + { + "pal8rlecut", { // Using palette tricks to skip pixels + {Default, nil, {127, 64, 3, 8}, 0x_bd04_3619}, + }, + }, + { + "rgb16faketrns", { // Using palette tricks to skip pixels + {Default, nil, {127, 64, 3, 8}, 0x_8b6f_81a2}, + }, + }, + { + "rgb16-231", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_7393_a163}, + }, + }, + { + "rgb16-3103", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_3b66_2189}, + }, + }, + { + "rgba16-4444", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_b785_1f9f}, + }, + }, + { + "rgba16-5551", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_8b6f_81a2}, + }, + }, + { + "rgba16-1924", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_f038_2bed}, + }, + }, + { + "rgb32-xbgr", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, + { + "rgb32fakealpha", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, + { + "rgb32-111110", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_b2c7_a8ff}, + }, + }, + { + "rgb32-7187", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_b93a_4291}, + }, + }, + { + "rgba32-1", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_7b67_823d}, + }, + }, + { + "rgba32-2", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_7b67_823d}, + }, + }, + { + "rgba32-1010102", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_aa42_0b16}, + }, + }, + { + "rgba32-81284", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_28a2_4c16}, + }, + }, + { + "rgba32-61754", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_4aae_26ed}, + }, + }, + { + "rgba32abf", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_7b67_823d}, + }, + }, + { + "rgb32h52", { // Truncated header (RGB bit fields included) + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, + { + "rgba32h56", { // Truncated header (RGBA bit fields included) + {Default, nil, {127, 64, 3, 8}, 0x_7b67_823d}, + }, + }, +} + +// Unsupported BMP features, or malformed images. +Unsupported_BMP_Tests := []Test{ + { + "ba-bm", { // An OS/2 Bitmap array. We don't support this BA format. + {Default, .Unsupported_OS2_File, {127, 32, 3, 8}, 0x_0000_0000}, + }, + }, + { + "pal1huffmsb", { // An OS/2 file with Huffman 1D compression + {Default, .Unsupported_Compression, {127, 32, 3, 8}, 0x_0000_0000}, + }, + }, + { + "rgb24rle24", { // An OS/2 file with RLE24 compression + {Default, .Unsupported_Compression, {127, 64, 3, 8}, 0x_0000_0000}, + }, + }, + { + "rgba64", { // An OS/2 file with RLE24 compression + {Default, .Unsupported_BPP, {127, 64, 3, 8}, 0x_0000_0000}, + }, + }, +} + +// Malformed / malicious files +Known_Bad_BMP_Tests := []Test{ + { + "badbitcount", { + {Default, .Unsupported_BPP, {127, 64, 3, 8}, 0x_3ce81fae}, + }, + }, + { + "badbitssize", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "baddens1", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "baddens2", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "badfilesize", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "badheadersize", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "badpalettesize", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "badplanes", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "badrle", { + {Default, nil, {127, 64, 3, 8}, 0x_1457_aae4}, + }, + }, + { + "badrle4", { + {Default, nil, {127, 64, 3, 8}, 0x_6764_d2ac}, + }, + }, + { + "badrle4bis", { + {Default, nil, {127, 64, 3, 8}, 0x_935d_bb37}, + }, + }, + { + "badrle4ter", { + {Default, nil, {127, 64, 3, 8}, 0x_f2ba_5b08}, + }, + }, + { + "badrlebis", { + {Default, nil, {127, 64, 3, 8}, 0x_07e2_d730}, + }, + }, + { + "badrleter", { + {Default, nil, {127, 64, 3, 8}, 0x_a874_2742}, + }, + }, + { + "badwidth", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "pal8badindex", { + {Default, nil, {127, 64, 3, 8}, 0x_0450_0d02}, + }, + }, + { + "reallybig", { + {Default, .Image_Dimensions_Too_Large, {3000000, 2000000, 1, 24}, 0x_0000_0000}, + }, + }, + { + "rgb16-880", { + {Default, nil, {127, 64, 3, 8}, 0x_f1c2_0c73}, + }, + }, + { + "rletopdown", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "shortfile", { + {Default, .Short_Buffer, {127, 64, 1, 1}, 0x_0000_0000}, + }, + }, +} + +@test +bmp_test_basic :: proc(t: ^testing.T) { + run_bmp_suite(t, Basic_BMP_Tests) +} + +@test +bmp_test_os2 :: proc(t: ^testing.T) { + run_bmp_suite(t, OS2_Tests) +} + +@test +bmp_test_questionable :: proc(t: ^testing.T) { + run_bmp_suite(t, Questionable_BMP_Tests) +} + +@test +bmp_test_unsupported :: proc(t: ^testing.T) { + run_bmp_suite(t, Unsupported_BMP_Tests) +} + +@test +bmp_test_known_bad :: proc(t: ^testing.T) { + run_bmp_suite(t, Known_Bad_BMP_Tests) +} + +run_bmp_suite :: proc(t: ^testing.T, suite: []Test) { + for file in suite { + test_file := strings.concatenate({TEST_SUITE_PATH_BMP, "/", file.file, ".bmp"}, context.allocator) + defer delete(test_file) + + for test in file.tests { + img, err := bmp.load(test_file, test.options) + + passed := (test.expected_error == nil && err == nil) || (test.expected_error == err) + testing.expectf(t, passed, "%q failed to load with error %v.", file.file, err) + + if err == nil { // No point in running the other tests if it didn't load. + pixels := bytes.buffer_to_bytes(&img.pixels) + + dims := Dims{img.width, img.height, img.channels, img.depth} + testing.expectf(t, test.dims == dims, "%v has %v, expected: %v.", file.file, dims, test.dims) + + img_hash := hash.crc32(pixels) + testing.expectf(t, test.hash == img_hash, "%v test #1's hash is %08x, expected %08x with %v.", file.file, img_hash, test.hash, test.options) + + // Save to BMP file in memory + buf: bytes.Buffer + save_err := bmp.save(&buf, img) + testing.expectf(t, save_err == nil, "expected saving to BMP in memory not to raise error, got %v", save_err) + + // Reload BMP from memory + reload_img, reload_err := bmp.load(buf.buf[:]) + testing.expectf(t, reload_err == nil, "expected reloading BMP from memory not to raise error, got %v", reload_err) + + testing.expect(t, img.width == reload_img.width && img.height == reload_img.height, "expected saved BMP to have the same dimensions") + testing.expect(t, img.channels == reload_img.channels && img.depth == reload_img.depth, "expected saved BMP to have the same dimensions") + + reload_pixels := bytes.buffer_to_bytes(&reload_img.pixels) + reload_hash := hash.crc32(reload_pixels) + + testing.expectf(t, img_hash == reload_hash, "expected saved BMP to have the same pixel hash (%08x), got %08x", img_hash, reload_hash) + + bytes.buffer_destroy(&buf) + bmp.destroy(reload_img) } + bmp.destroy(img) + } + } + return +} + +@test +will_it_blend :: proc(t: ^testing.T) { + Pixel :: image.RGB_Pixel + Pixel_16 :: image.RGB_Pixel_16 + + { + bg := Pixel{255, 255, 0} + fg := Pixel{ 0, 0, 255} + + for a in 0..=255 { + blended := Pixel{ + image.blend(fg.r, u8(a), bg.r), + image.blend(fg.g, u8(a), bg.g), + image.blend(fg.b, u8(a), bg.b), + } + testing.expectf(t, blended.r == bg.r - u8(a), "Expected blend(%v, %3d, %v) = %v, got %v", fg.r, a, bg.r, bg.r - u8(a), blended.r) + testing.expectf(t, blended.b == 255 - blended.r, "Expected blend(%v, %3d, %v) = %v, got %v", fg.b, a, bg.b, 255 - blended.r, blended.b) } } - return -} + { + bg := Pixel_16{65535, 65535, 0} + fg := Pixel_16{ 0, 0, 65535} + + for a in 0..=65535 { + blended := Pixel_16{ + image.blend(fg.r, u16(a), bg.r), + image.blend(fg.g, u16(a), bg.g), + image.blend(fg.b, u16(a), bg.b), + } + testing.expectf(t, blended.r == bg.r - u16(a), "Expected blend(%v, %3d, %v) = %v, got %v", fg.r, a, bg.r, bg.r - u16(a), blended.r) + testing.expectf(t, blended.b == 65535 - blended.r, "Expected blend(%v, %3d, %v) = %v, got %v", fg.b, a, bg.b, 65535 - blended.r, blended.b) + } + } +} \ No newline at end of file diff --git a/tests/core/math/big/test.odin b/tests/core/math/big/test.odin index e0762a66d..f35f0b72b 100644 --- a/tests/core/math/big/test.odin +++ b/tests/core/math/big/test.odin @@ -8,7 +8,7 @@ This file exports procedures for use with the test.py test suite. */ -package math_big_tests +package test_core_math_big /* TODO: Write tests for `internal_*` and test reusing parameters with the public implementations. diff --git a/tests/core/math/big/test_core_math_big.odin b/tests/core/math/big/test_core_math_big.odin new file mode 100644 index 000000000..9a1e7b01b --- /dev/null +++ b/tests/core/math/big/test_core_math_big.odin @@ -0,0 +1,37 @@ +package test_core_math_big + +import "core:math/big" +import "core:testing" + +@(test) +test_permutations_and_combinations :: proc(t: ^testing.T) { + { + calc, exp := &big.Int{}, &big.Int{} + defer big.destroy(calc, exp) + big.permutations_without_repetition(calc, 9000, 10) + big.int_atoi(exp, "3469387884476822917768284664849390080000") + equals, error := big.equals(calc, exp) + testing.expect(t, equals) + testing.expect_value(t, error, nil) + } + + { + calc, exp := &big.Int{}, &big.Int{} + defer big.destroy(calc, exp) + big.combinations_with_repetition(calc, 9000, 10) + big.int_atoi(exp, "965678962435231708695393645683400") + equals, error := big.equals(calc, exp) + testing.expect(t, equals) + testing.expect_value(t, error, nil) + } + + { + calc, exp := &big.Int{}, &big.Int{} + defer big.destroy(calc, exp) + big.combinations_without_repetition(calc, 9000, 10) + big.int_atoi(exp, "956070294443568925751842114431600") + equals, error := big.equals(calc, exp) + testing.expect(t, equals) + testing.expect_value(t, error, nil) + } +} diff --git a/tests/core/math/fixed/test_core_math_fixed.odin b/tests/core/math/fixed/test_core_math_fixed.odin new file mode 100644 index 000000000..4fcd63422 --- /dev/null +++ b/tests/core/math/fixed/test_core_math_fixed.odin @@ -0,0 +1,51 @@ +package test_core_math_fixed + +import "core:math/fixed" +import "core:testing" + +@test +test_fixed_4_4_unsigned :: proc(t: ^testing.T) { + I_SHIFT :: 4 + F_MASK :: 15 + F_ULP :: 0.0625 + Fixed :: fixed.Fixed(u8, 4) + + for c in 0..<256 { + raw := u8(c) + fv := transmute(Fixed)raw + + i := raw >> I_SHIFT + f := raw & F_MASK + expected := f64(i) + F_ULP * f64(f) + + testing.expectf(t, fixed.to_f64(fv) == expected, "Expected Fixed(u8, 4)(%v) to equal %.5f, got %.5f", raw, expected, fixed.to_f64(fv)) + } +} + +@test +test_fixed_4_4_signed :: proc(t: ^testing.T) { + I_SHIFT :: 4 + F_MASK :: 15 + F_ULP :: 0.0625 + Fixed :: fixed.Fixed(i8, 4) + + for c in 0..<256 { + raw := i8(c) + fv := transmute(Fixed)raw + + f := raw & F_MASK + expected: f64 + if c < 128 { + i := raw >> I_SHIFT + expected = f64(i) + F_ULP * f64(f) + } else if c == 128 { + expected = 8.0 + + } else if c > 128 { + i := i8(-8) + i += (raw & 0b0111_0000) >> I_SHIFT + expected = f64(i) + F_ULP * f64(f) + } + testing.expectf(t, fixed.to_f64(fv) == expected, "Expected Fixed(i8, 4)(%v, %v) to equal %.5f, got %.5f", c, raw, expected, fixed.to_f64(fv)) + } +} \ No newline at end of file diff --git a/tests/core/math/linalg/glsl/test_linalg_glsl_math.odin b/tests/core/math/linalg/glsl/test_linalg_glsl_math.odin index cf91b8a97..6d4571b24 100644 --- a/tests/core/math/linalg/glsl/test_linalg_glsl_math.odin +++ b/tests/core/math/linalg/glsl/test_linalg_glsl_math.odin @@ -1,24 +1,10 @@ // Tests "linalg_glsl_math.odin" in "core:math/linalg/glsl". -// Must be run with `-collection:tests=` flag, e.g. -// ./odin run tests/core/math/linalg/glsl/test_linalg_glsl_math.odin -collection:tests=./tests package test_core_math_linalg_glsl_math import glsl "core:math/linalg/glsl" -import "core:fmt" import "core:math" import "core:testing" -import tc "tests:common" - -main :: proc() { - - t := testing.T{} - - test_fract_f32(&t) - test_fract_f64(&t) - - tc.report(&t) -} @test test_fract_f32 :: proc(t: ^testing.T) { @@ -45,7 +31,7 @@ test_fract_f32 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = glsl.fract(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%v (%h)) -> %v (%h) != %v", i, #procedure, d.v, d.v, r, r, d.e)) + testing.expectf(t, r == d.e, "%v (%h) -> %v (%h) != %v", d.v, d.v, r, r, d.e) } } @@ -74,6 +60,6 @@ test_fract_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = glsl.fract(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%v (%h)) -> %v (%h) != %v", i, #procedure, d.v, d.v, r, r, d.e)) + testing.expectf(t, r == d.e, "%v (%h) -> %v (%h) != %v", d.v, d.v, r, r, d.e) } -} +} \ No newline at end of file diff --git a/tests/core/math/noise/test_core_math_noise.odin b/tests/core/math/noise/test_core_math_noise.odin index a0360e695..f835cf58c 100644 --- a/tests/core/math/noise/test_core_math_noise.odin +++ b/tests/core/math/noise/test_core_math_noise.odin @@ -2,42 +2,6 @@ package test_core_math_noise import "core:testing" import "core:math/noise" -import "core:fmt" -import "core:os" - -TEST_count := 0 -TEST_fail := 0 - -V2 :: noise.Vec2 -V3 :: noise.Vec3 -V4 :: noise.Vec4 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - noise_test(&t) - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} Test_Vector :: struct { seed: i64, @@ -51,6 +15,10 @@ Test_Vector :: struct { }, } +V2 :: noise.Vec2 +V3 :: noise.Vec3 +V4 :: noise.Vec4 + SEED_1 :: 2324223232 SEED_2 :: 932466901 SEED_3 :: 9321 @@ -59,93 +27,78 @@ COORD_1 :: V4{ 242.0, 3433.0, 920.0, 222312.0} COORD_2 :: V4{ 590.0, 9411.0, 5201.0, 942124256.0} COORD_3 :: V4{12090.0, 19411.0, 81950901.0, 4224219.0} -Noise_Tests := []Test_Vector{ - /* - `noise_2d` tests. - */ - {SEED_1, COORD_1.xy, 0.25010583, noise.noise_2d}, - {SEED_2, COORD_2.xy, -0.92513955, noise.noise_2d}, - {SEED_3, COORD_3.xy, 0.67327416, noise.noise_2d}, - - /* - `noise_2d_improve_x` tests. - */ - {SEED_1, COORD_1.xy, 0.17074019, noise.noise_2d_improve_x}, - {SEED_2, COORD_2.xy, 0.72330487, noise.noise_2d_improve_x}, - {SEED_3, COORD_3.xy, -0.032076947, noise.noise_2d_improve_x}, - - /* - `noise_3d_improve_xy` tests. - */ - {SEED_1, COORD_1.xyz, 0.14819577, noise.noise_3d_improve_xy}, - {SEED_2, COORD_2.xyz, -0.065345764, noise.noise_3d_improve_xy}, - {SEED_3, COORD_3.xyz, -0.37761918, noise.noise_3d_improve_xy}, - - /* - `noise_3d_improve_xz` tests. - */ - {SEED_1, COORD_1.xyz, -0.50075006, noise.noise_3d_improve_xz}, - {SEED_2, COORD_2.xyz, -0.36039603, noise.noise_3d_improve_xz}, - {SEED_3, COORD_3.xyz, -0.3479203, noise.noise_3d_improve_xz}, - - /* - `noise_3d_fallback` tests. - */ - {SEED_1, COORD_1.xyz, 0.6557345, noise.noise_3d_fallback}, - {SEED_2, COORD_2.xyz, 0.55452216, noise.noise_3d_fallback}, - {SEED_3, COORD_3.xyz, -0.26408964, noise.noise_3d_fallback}, - - /* - `noise_3d_fallback` tests. - */ - {SEED_1, COORD_1.xyz, 0.6557345, noise.noise_3d_fallback}, - {SEED_2, COORD_2.xyz, 0.55452216, noise.noise_3d_fallback}, - {SEED_3, COORD_3.xyz, -0.26408964, noise.noise_3d_fallback}, - - /* - `noise_4d_improve_xyz_improve_xy` tests. - */ - {SEED_1, COORD_1, 0.44929826, noise.noise_4d_improve_xyz_improve_xy}, - {SEED_2, COORD_2, -0.13270882, noise.noise_4d_improve_xyz_improve_xy}, - {SEED_3, COORD_3, 0.10298563, noise.noise_4d_improve_xyz_improve_xy}, - - /* - `noise_4d_improve_xyz_improve_xz` tests. - */ - {SEED_1, COORD_1, -0.078514606, noise.noise_4d_improve_xyz_improve_xz}, - {SEED_2, COORD_2, -0.032157656, noise.noise_4d_improve_xyz_improve_xz}, - {SEED_3, COORD_3, -0.38607058, noise.noise_4d_improve_xyz_improve_xz}, - - /* - `noise_4d_improve_xyz` tests. - */ - {SEED_1, COORD_1, -0.4442258, noise.noise_4d_improve_xyz}, - {SEED_2, COORD_2, 0.36822623, noise.noise_4d_improve_xyz}, - {SEED_3, COORD_3, 0.22628775, noise.noise_4d_improve_xyz}, - - /* - `noise_4d_fallback` tests. - */ - {SEED_1, COORD_1, -0.14233987, noise.noise_4d_fallback}, - {SEED_2, COORD_2, 0.1354035, noise.noise_4d_fallback}, - {SEED_3, COORD_3, 0.14565045, noise.noise_4d_fallback}, - +@(test) +test_noise_2d :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xy, 0.25010583, noise.noise_2d}) + test(t, {SEED_2, COORD_2.xy, -0.92513955, noise.noise_2d}) + test(t, {SEED_3, COORD_3.xy, 0.67327416, noise.noise_2d}) } -noise_test :: proc(t: ^testing.T) { - for test in Noise_Tests { - output: f32 +@(test) +test_noise_2d_improve_x :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xy, 0.17074019, noise.noise_2d_improve_x}) + test(t, {SEED_2, COORD_2.xy, 0.72330487, noise.noise_2d_improve_x}) + test(t, {SEED_3, COORD_3.xy, -0.032076947, noise.noise_2d_improve_x}) +} - switch coord in test.coord { - case V2: - output = test.test_proc.(proc(_: i64, _: V2) -> f32)(test.seed, test.coord.(V2)) - case V3: - output = test.test_proc.(proc(_: i64, _: V3) -> f32)(test.seed, test.coord.(V3)) - case V4: - output = test.test_proc.(proc(_: i64, _: V4) -> f32)(test.seed, test.coord.(V4)) - } - - error := fmt.tprintf("Seed %v, Coord: %v, Expected: %3.8f. Got %3.8f", test.seed, test.coord, test.expected, output) - expect(t, test.expected == output, error) +@(test) +test_noise_3d_improve_xy :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xyz, 0.14819577, noise.noise_3d_improve_xy}) + test(t, {SEED_2, COORD_2.xyz, -0.065345764, noise.noise_3d_improve_xy}) + test(t, {SEED_3, COORD_3.xyz, -0.37761918, noise.noise_3d_improve_xy}) +} + +@(test) +test_noise_3d_improve_xz :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xyz, -0.50075006, noise.noise_3d_improve_xz}) + test(t, {SEED_2, COORD_2.xyz, -0.36039603, noise.noise_3d_improve_xz}) + test(t, {SEED_3, COORD_3.xyz, -0.3479203, noise.noise_3d_improve_xz}) +} + +@(test) +test_noise_3d_fallback :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xyz, 0.6557345, noise.noise_3d_fallback}) + test(t, {SEED_2, COORD_2.xyz, 0.55452216, noise.noise_3d_fallback}) + test(t, {SEED_3, COORD_3.xyz, -0.26408964, noise.noise_3d_fallback}) +} + +@(test) +test_noise_4d_improve_xyz_improve_xy :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1, 0.44929826, noise.noise_4d_improve_xyz_improve_xy}) + test(t, {SEED_2, COORD_2, -0.13270882, noise.noise_4d_improve_xyz_improve_xy}) + test(t, {SEED_3, COORD_3, 0.10298563, noise.noise_4d_improve_xyz_improve_xy}) +} + +@(test) +test_noise_4d_improve_xyz_improve_xz :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1, -0.078514606, noise.noise_4d_improve_xyz_improve_xz}) + test(t, {SEED_2, COORD_2, -0.032157656, noise.noise_4d_improve_xyz_improve_xz}) + test(t, {SEED_3, COORD_3, -0.38607058, noise.noise_4d_improve_xyz_improve_xz}) +} + +@(test) +test_noise_4d_improve_xyz :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1, -0.4442258, noise.noise_4d_improve_xyz}) + test(t, {SEED_2, COORD_2, 0.36822623, noise.noise_4d_improve_xyz}) + test(t, {SEED_3, COORD_3, 0.22628775, noise.noise_4d_improve_xyz}) +} + +@(test) +test_noise_4d_fallback :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1, -0.14233987, noise.noise_4d_fallback}) + test(t, {SEED_2, COORD_2, 0.1354035, noise.noise_4d_fallback}) + test(t, {SEED_3, COORD_3, 0.14565045, noise.noise_4d_fallback}) +} + +test :: proc(t: ^testing.T, test: Test_Vector) { + output: f32 + switch coord in test.coord { + case V2: + output = test.test_proc.(proc(_: i64, _: V2) -> f32)(test.seed, test.coord.(V2)) + case V3: + output = test.test_proc.(proc(_: i64, _: V3) -> f32)(test.seed, test.coord.(V3)) + case V4: + output = test.test_proc.(proc(_: i64, _: V4) -> f32)(test.seed, test.coord.(V4)) } + testing.expectf(t, test.expected == output, "Seed %v, Coord: %v, Expected: %3.8f. Got %3.8f", test.seed, test.coord, test.expected, output) } \ No newline at end of file diff --git a/tests/core/math/rand/test_core_math_rand.odin b/tests/core/math/rand/test_core_math_rand.odin new file mode 100644 index 000000000..392d3d241 --- /dev/null +++ b/tests/core/math/rand/test_core_math_rand.odin @@ -0,0 +1,35 @@ +package test_core_math_rand + +import "core:math/rand" +import "core:testing" + +@test +test_default_rand_determinism :: proc(t: ^testing.T) { + rand.reset(13) + first_value := rand.int127() + rand.reset(13) + second_value := rand.int127() + + testing.expect(t, first_value == second_value, "Context default random number generator is non-deterministic.") +} + +@test +test_default_rand_determinism_user_set :: proc(t: ^testing.T) { + rng_state_1 := rand.create(13) + rng_state_2 := rand.create(13) + + rng_1 := rand.default_random_generator(&rng_state_1) + rng_2 := rand.default_random_generator(&rng_state_2) + + first_value, second_value: i128 + { + context.random_generator = rng_1 + first_value = rand.int127() + } + { + context.random_generator = rng_2 + second_value = rand.int127() + } + + testing.expect(t, first_value == second_value, "User-set default random number generator is non-deterministic.") +} diff --git a/tests/core/math/test_core_math.odin b/tests/core/math/test_core_math.odin index df989bff6..2a752e366 100644 --- a/tests/core/math/test_core_math.odin +++ b/tests/core/math/test_core_math.odin @@ -1,49 +1,8 @@ // Tests "math.odin" in "core:math". -// Must be run with `-collection:tests=` flag, e.g. -// ./odin run tests/core/math/test_core_math.odin -collection:tests=./tests package test_core_math -import "core:fmt" import "core:math" import "core:testing" -import tc "tests:common" - -main :: proc() { - t := testing.T{} - - test_classify_f16(&t) - test_classify_f32(&t) - test_classify_f64(&t) - - test_trunc_f16(&t) - test_trunc_f32(&t) - test_trunc_f64(&t) - - test_round_f16(&t) - test_round_f32(&t) - test_round_f64(&t) - - test_nan(&t) - test_acos(&t) - test_acosh(&t) - test_asin(&t) - test_asinh(&t) - test_atan(&t) - test_atanh(&t) - test_atan2(&t) - test_cos(&t) - test_cosh(&t) - test_sin(&t) - test_sinh(&t) - test_sqrt(&t) - test_tan(&t) - test_tanh(&t) - test_large_cos(&t) - test_large_sin(&t) - test_large_tan(&t) - - tc.report(&t) -} @test test_classify_f16 :: proc(t: ^testing.T) { @@ -68,7 +27,7 @@ test_classify_f16 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.classify_f16(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %v != %v", d.v, r, d.e) } /* Check all subnormals (exponent 0, 10-bit significand non-zero) */ @@ -76,7 +35,7 @@ test_classify_f16 :: proc(t: ^testing.T) { v := transmute(f16)i r = math.classify_f16(v) e :: math.Float_Class.Subnormal - tc.expect(t, r == e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, v, r, e)) + testing.expectf(t, r == e, "%h -> %v != %v", v, r, e) } } @@ -103,7 +62,7 @@ test_classify_f32 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.classify_f32(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %v != %v", d.v, r, d.e) } } @@ -130,7 +89,7 @@ test_classify_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.classify_f64(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %v != %v", d.v, r, d.e) } } @@ -175,16 +134,16 @@ test_trunc_f16 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.trunc_f16(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F16 r = math.trunc_f16(v) - tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f16(r), "%f != NaN", v, r) v = math.QNAN_F16 r = math.trunc_f16(v) - tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f16(r), "%f != NaN", v, r) } @test @@ -237,16 +196,16 @@ test_trunc_f32 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.trunc_f32(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F32 r = math.trunc_f32(v) - tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r) v = math.QNAN_F32 r = math.trunc_f32(v) - tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r) } @test @@ -299,16 +258,16 @@ test_trunc_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.trunc_f64(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F64 r = math.trunc_f64(v) - tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r) v = math.QNAN_F64 r = math.trunc_f64(v) - tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r) } @test @@ -352,16 +311,16 @@ test_round_f16 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.round_f16(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F16 r = math.round_f16(v) - tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f16(r), "%f -> %f != NaN", v, r) v = math.QNAN_F16 r = math.round_f16(v) - tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f16(r), "%f -> %f != NaN", v, r) } @test @@ -414,16 +373,16 @@ test_round_f32 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.round_f32(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", i, d.v, r, d.e) } v = math.SNAN_F32 r = math.round_f32(v) - tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r) v = math.QNAN_F32 r = math.round_f32(v) - tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r) } @test @@ -476,16 +435,16 @@ test_round_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.round_f64(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F64 r = math.round_f64(v) - tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r) v = math.QNAN_F64 r = math.round_f64(v) - tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r) } @@ -1033,17 +992,17 @@ tolerance :: proc(a, b, e: f64) -> bool { } close :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool { ok := tolerance(a, b, 1e-9) - // tc.expect(t, ok, fmt.tprintf("%.15g is not close to %.15g", a, b), loc) + testing.expectf(t, ok, "%.15g is not close to %.15g", a, b, loc=loc) return ok } veryclose :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool { ok := tolerance(a, b, 4e-14) - // tc.expect(t, ok, fmt.tprintf("%.15g is not veryclose to %.15g", a, b), loc) + testing.expectf(t, ok, "%.15g is not veryclose to %.15g", a, b, loc=loc) return ok } soclose :: proc(t: ^testing.T, a, b, e: f64, loc := #caller_location) -> bool { ok := tolerance(a, b, e) - // tc.expect(t, ok, fmt.tprintf("%.15g is not soclose to %.15g", a, b), loc) + testing.expectf(t, ok, "%.15g is not soclose to %.15g", a, b, loc=loc) return ok } alike :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool { @@ -1054,34 +1013,34 @@ alike :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool { case a == b: ok = math.signbit(a) == math.signbit(b) } - // tc.expect(t, ok, fmt.tprintf("%.15g is not alike to %.15g", a, b), loc) + testing.expectf(t, ok, "%.15g is not alike to %.15g", a, b, loc=loc) return ok } @test -test_nan :: proc(t: ^testing.T) { +test_nan32 :: proc(t: ^testing.T) { + float32 := f32(NaN) + equal := float32 == float32 + testing.expectf(t, !equal, "float32(NaN) is %.15g, expected NaN", float32) +} + +@test +test_nan64 :: proc(t: ^testing.T) { float64 := NaN - if float64 == float64 { - tc.errorf(t, "NaN returns %.15g, expected NaN", float64) - } - float32 := f32(float64) - if float32 == float32 { - tc.errorf(t, "float32(NaN) is %.15g, expected NaN", float32) - } + equal := float64 == float64 + testing.expectf(t, !equal, "NaN returns %.15g, expected NaN", float64) } @test test_acos :: proc(t: ^testing.T) { for _, i in vf { a := vf[i] / 10 - if f := math.acos(a); !close(t, acos[i], f) { - tc.errorf(t, "math.acos(%.15g) = %.15g, want %.15g", a, f, acos[i]) - } + f := math.acos(a) + testing.expectf(t, close(t, acos[i], f), "math.acos(%.15g) = %.15g, want %.15g", a, f, acos[i]) } for _, i in vfacos_sc { - if f := math.acos(vfacos_sc[i]); !alike(t, acos_sc[i], f) { - tc.errorf(t, "math.acos(%.15g) = %.15g, want %.15g", vfacos_sc[i], f, acos_sc[i]) - } + f := math.acos(vfacos_sc[i]) + testing.expectf(t, alike(t, acos_sc[i], f), "math.acos(%.15g) = %.15g, want %.15g", vfacos_sc[i], f, acos_sc[i]) } } @@ -1089,14 +1048,12 @@ test_acos :: proc(t: ^testing.T) { test_acosh :: proc(t: ^testing.T) { for _, i in vf { a := 1 + abs(vf[i]) - if f := math.acosh(a); !veryclose(t, acosh[i], f) { - tc.errorf(t, "math.acosh(%.15g) = %.15g, want %.15g", a, f, acosh[i]) - } + f := math.acosh(a) + testing.expectf(t, veryclose(t, acosh[i], f), "math.acosh(%.15g) = %.15g, want %.15g", a, f, acosh[i]) } for _, i in vfacosh_sc { - if f := math.acosh(vfacosh_sc[i]); !alike(t, acosh_sc[i], f) { - tc.errorf(t, "math.acosh(%.15g) = %.15g, want %.15g", vfacosh_sc[i], f, acosh_sc[i]) - } + f := math.acosh(vfacosh_sc[i]) + testing.expectf(t, alike(t, acosh_sc[i], f), "math.acosh(%.15g) = %.15g, want %.15g", vfacosh_sc[i], f, acosh_sc[i]) } } @@ -1104,42 +1061,36 @@ test_acosh :: proc(t: ^testing.T) { test_asin :: proc(t: ^testing.T) { for _, i in vf { a := vf[i] / 10 - if f := math.asin(a); !veryclose(t, asin[i], f) { - tc.errorf(t, "math.asin(%.15g) = %.15g, want %.15g", a, f, asin[i]) - } + f := math.asin(a) + testing.expectf(t, veryclose(t, asin[i], f), "math.asin(%.15g) = %.15g, want %.15g", a, f, asin[i]) } for _, i in vfasin_sc { - if f := math.asin(vfasin_sc[i]); !alike(t, asin_sc[i], f) { - tc.errorf(t, "math.asin(%.15g) = %.15g, want %.15g", vfasin_sc[i], f, asin_sc[i]) - } + f := math.asin(vfasin_sc[i]) + testing.expectf(t, alike(t, asin_sc[i], f), "math.asin(%.15g) = %.15g, want %.15g", vfasin_sc[i], f, asin_sc[i]) } } @test test_asinh :: proc(t: ^testing.T) { for _, i in vf { - if f := math.asinh(vf[i]); !veryclose(t, asinh[i], f) { - tc.errorf(t, "math.asinh(%.15g) = %.15g, want %.15g", vf[i], f, asinh[i]) - } + f := math.asinh(vf[i]) + testing.expectf(t, veryclose(t, asinh[i], f), "math.asinh(%.15g) = %.15g, want %.15g", vf[i], f, asinh[i]) } for _, i in vfasinh_sc { - if f := math.asinh(vfasinh_sc[i]); !alike(t, asinh_sc[i], f) { - tc.errorf(t, "math.asinh(%.15g) = %.15g, want %.15g", vfasinh_sc[i], f, asinh_sc[i]) - } + f := math.asinh(vfasinh_sc[i]) + testing.expectf(t, alike(t, asinh_sc[i], f), "math.asinh(%.15g) = %.15g, want %.15g", vfasinh_sc[i], f, asinh_sc[i]) } } @test test_atan :: proc(t: ^testing.T) { for _, i in vf { - if f := math.atan(vf[i]); !veryclose(t, atan[i], f) { - tc.errorf(t, "math.atan(%.15g) = %.15g, want %.15g", vf[i], f, atan[i]) - } + f := math.atan(vf[i]) + testing.expectf(t, veryclose(t, atan[i], f), "math.atan(%.15g) = %.15g, want %.15g", vf[i], f, atan[i]) } for _, i in vfatan_sc { - if f := math.atan(vfatan_sc[i]); !alike(t, atan_sc[i], f) { - tc.errorf(t, "math.atan(%.15g) = %.15g, want %.15g", vfatan_sc[i], f, atan_sc[i]) - } + f := math.atan(vfatan_sc[i]) + testing.expectf(t, alike(t, atan_sc[i], f), "math.atan(%.15g) = %.15g, want %.15g", vfatan_sc[i], f, atan_sc[i]) } } @@ -1147,84 +1098,72 @@ test_atan :: proc(t: ^testing.T) { test_atanh :: proc(t: ^testing.T) { for _, i in vf { a := vf[i] / 10 - if f := math.atanh(a); !veryclose(t, atanh[i], f) { - tc.errorf(t, "math.atanh(%.15g) = %.15g, want %.15g", a, f, atanh[i]) - } + f := math.atanh(a) + testing.expectf(t, veryclose(t, atanh[i], f), "math.atanh(%.15g) = %.15g, want %.15g", a, f, atanh[i]) } for _, i in vfatanh_sc { - if f := math.atanh(vfatanh_sc[i]); !alike(t, atanh_sc[i], f) { - tc.errorf(t, "math.atanh(%.15g) = %.15g, want %.15g", vfatanh_sc[i], f, atanh_sc[i]) - } + f := math.atanh(vfatanh_sc[i]) + testing.expectf(t, alike(t, atanh_sc[i], f), "math.atanh(%.15g) = %.15g, want %.15g", vfatanh_sc[i], f, atanh_sc[i]) } } @test test_atan2 :: proc(t: ^testing.T) { for _, i in vf { - if f := math.atan2(10, vf[i]); !veryclose(t, atan2[i], f) { - tc.errorf(t, "math.atan2(10, %.15g) = %.15g, want %.15g", vf[i], f, atan2[i]) - } + f := math.atan2(10, vf[i]) + testing.expectf(t, veryclose(t, atan2[i], f), "math.atan2(10, %.15g) = %.15g, want %.15g", vf[i], f, atan2[i]) } for _, i in vfatan2_sc { - if f := math.atan2(vfatan2_sc[i][0], vfatan2_sc[i][1]); !alike(t, atan2_sc[i], f) { - tc.errorf(t, "math.atan2(%.15g, %.15g) = %.15g, want %.15g", vfatan2_sc[i][0], vfatan2_sc[i][1], f, atan2_sc[i]) - } + f := math.atan2(vfatan2_sc[i][0], vfatan2_sc[i][1]) + testing.expectf(t, alike(t, atan2_sc[i], f), "math.atan2(%.15g, %.15g) = %.15g, want %.15g", vfatan2_sc[i][0], vfatan2_sc[i][1], f, atan2_sc[i]) } } @test test_cos :: proc(t: ^testing.T) { for _, i in vf { - if f := math.cos(vf[i]); !veryclose(t, cos[i], f) { - tc.errorf(t, "math.cos(%.15g) = %.15g, want %.15g", vf[i], f, cos[i]) - } + f := math.cos(vf[i]) + testing.expectf(t, veryclose(t, cos[i], f), "math.cos(%.15g) = %.15g, want %.15g", vf[i], f, cos[i]) } for _, i in vfcos_sc { - if f := math.cos(vfcos_sc[i]); !alike(t, cos_sc[i], f) { - tc.errorf(t, "math.cos(%.15g) = %.15g, want %.15g", vfcos_sc[i], f, cos_sc[i]) - } + f := math.cos(vfcos_sc[i]) + testing.expectf(t, alike(t, cos_sc[i], f), "math.cos(%.15g) = %.15g, want %.15g", vfcos_sc[i], f, cos_sc[i]) } } @test test_cosh :: proc(t: ^testing.T) { for _, i in vf { - if f := math.cosh(vf[i]); !close(t, cosh[i], f) { - tc.errorf(t, "math.cosh(%.15g) = %.15g, want %.15g", vf[i], f, cosh[i]) - } + f := math.cosh(vf[i]) + testing.expectf(t, close(t, cosh[i], f), "math.cosh(%.15g) = %.15g, want %.15g", vf[i], f, cosh[i]) } for _, i in vfcosh_sc { - if f := math.cosh(vfcosh_sc[i]); !alike(t, cosh_sc[i], f) { - tc.errorf(t, "math.cosh(%.15g) = %.15g, want %.15g", vfcosh_sc[i], f, cosh_sc[i]) - } + f := math.cosh(vfcosh_sc[i]) + testing.expectf(t, alike(t, cosh_sc[i], f), "math.cosh(%.15g) = %.15g, want %.15g", vfcosh_sc[i], f, cosh_sc[i]) } } @test test_sin :: proc(t: ^testing.T) { for _, i in vf { - if f := math.sin(vf[i]); !veryclose(t, sin[i], f) { - tc.errorf(t, "math.sin(%.15g) = %.15g, want %.15g", vf[i], f, sin[i]) - } + f := math.sin(vf[i]) + testing.expectf(t, veryclose(t, sin[i], f), "math.sin(%.15g) = %.15g, want %.15g", vf[i], f, sin[i]) } for _, i in vfsin_sc { - if f := math.sin(vfsin_sc[i]); !alike(t, sin_sc[i], f) { - tc.errorf(t, "math.sin(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i]) - } + f := math.sin(vfsin_sc[i]) + testing.expectf(t, alike(t, sin_sc[i], f), "math.sin(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i]) } } @test test_sinh :: proc(t: ^testing.T) { for _, i in vf { - if f := math.sinh(vf[i]); !close(t, sinh[i], f) { - tc.errorf(t, "math.sinh(%.15g) = %.15g, want %.15g", vf[i], f, sinh[i]) - } + f := math.sinh(vf[i]) + testing.expectf(t, close(t, sinh[i], f), "math.sinh(%.15g) = %.15g, want %.15g", vf[i], f, sinh[i]) } for _, i in vfsinh_sc { - if f := math.sinh(vfsinh_sc[i]); !alike(t, sinh_sc[i], f) { - tc.errorf(t, "math.sinh(%.15g) = %.15g, want %.15g", vfsinh_sc[i], f, sinh_sc[i]) - } + f := math.sinh(vfsinh_sc[i]) + testing.expectf(t, alike(t, sinh_sc[i], f), "math.sinh(%.15g) = %.15g, want %.15g", vfsinh_sc[i], f, sinh_sc[i]) } } @@ -1232,38 +1171,33 @@ test_sinh :: proc(t: ^testing.T) { test_sqrt :: proc(t: ^testing.T) { for _, i in vf { a := abs(vf[i]) - if f := math.sqrt(a); !veryclose(t, sqrt[i], f) { - tc.errorf(t, "math.sqrt(%.15g) = %.15g, want %.15g", a, f, sqrt[i]) - } + f := math.sqrt(a) + testing.expectf(t, veryclose(t, sqrt[i], f), "math.sqrt(%.15g) = %.15g, want %.15g", a, f, sqrt[i]) } } @test test_tan :: proc(t: ^testing.T) { for _, i in vf { - if f := math.tan(vf[i]); !veryclose(t, tan[i], f) { - tc.errorf(t, "math.tan(%.15g) = %.15g, want %.15g", vf[i], f, tan[i]) - } + f := math.tan(vf[i]) + testing.expectf(t, veryclose(t, tan[i], f), "math.tan(%.15g) = %.15g, want %.15g", vf[i], f, tan[i]) } // same special cases as Sin for _, i in vfsin_sc { - if f := math.tan(vfsin_sc[i]); !alike(t, sin_sc[i], f) { - tc.errorf(t, "math.tan(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i]) - } + f := math.tan(vfsin_sc[i]) + testing.expectf(t, alike(t, sin_sc[i], f), "math.tan(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i]) } } @test test_tanh :: proc(t: ^testing.T) { for _, i in vf { - if f := math.tanh(vf[i]); !veryclose(t, tanh[i], f) { - tc.errorf(t, "math.tanh(%.15g) = %.15g, want %.15g", vf[i], f, tanh[i]) - } + f := math.tanh(vf[i]) + testing.expectf(t, veryclose(t, tanh[i], f), "math.tanh(%.15g) = %.15g, want %.15g", vf[i], f, tanh[i]) } for _, i in vftanh_sc { - if f := math.tanh(vftanh_sc[i]); !alike(t, tanh_sc[i], f) { - tc.errorf(t, "math.tanh(%.15g) = %.15g, want %.15g", vftanh_sc[i], f, tanh_sc[i]) - } + f := math.tanh(vftanh_sc[i]) + testing.expectf(t, alike(t, tanh_sc[i], f), "math.tanh(%.15g) = %.15g, want %.15g", vftanh_sc[i], f, tanh_sc[i]) } } @@ -1273,9 +1207,7 @@ test_large_cos :: proc(t: ^testing.T) { for _, i in vf { f1 := cosLarge[i] f2 := math.cos(vf[i] + large) - if !close(t, f1, f2) { - tc.errorf(t, "math.cos(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) - } + testing.expectf(t, close(t, f1, f2), "math.cos(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) } } @@ -1285,9 +1217,7 @@ test_large_sin :: proc(t: ^testing.T) { for _, i in vf { f1 := sinLarge[i] f2 := math.sin(vf[i] + large) - if !close(t, f1, f2) { - tc.errorf(t, "math.sin(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) - } + testing.expectf(t, close(t, f1, f2), "math.sin(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) } } @@ -1297,8 +1227,6 @@ test_large_tan :: proc(t: ^testing.T) { for _, i in vf { f1 := tanLarge[i] f2 := math.tan(vf[i] + large) - if !close(t, f1, f2) { - tc.errorf(t, "math.tan(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) - } + testing.expectf(t, close(t, f1, f2), "math.tan(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) } } \ No newline at end of file diff --git a/tests/core/mem/test_core_mem.odin b/tests/core/mem/test_core_mem.odin new file mode 100644 index 000000000..d282ae1fd --- /dev/null +++ b/tests/core/mem/test_core_mem.odin @@ -0,0 +1,41 @@ +package test_core_mem + +import "core:mem/tlsf" +import "core:testing" + +@test +test_tlsf_bitscan :: proc(t: ^testing.T) { + Vector :: struct { + op: enum{ffs, fls, fls_uint}, + v: union{u32, uint}, + exp: i32, + } + Tests := []Vector{ + {.ffs, u32 (0x0000_0000_0000_0000), -1}, + {.ffs, u32 (0x0000_0000_0000_0000), -1}, + {.fls, u32 (0x0000_0000_0000_0000), -1}, + {.ffs, u32 (0x0000_0000_0000_0001), 0}, + {.fls, u32 (0x0000_0000_0000_0001), 0}, + {.ffs, u32 (0x0000_0000_8000_0000), 31}, + {.ffs, u32 (0x0000_0000_8000_8000), 15}, + {.fls, u32 (0x0000_0000_8000_0008), 31}, + {.fls, u32 (0x0000_0000_7FFF_FFFF), 30}, + {.fls_uint, uint(0x0000_0000_8000_0000), 31}, + {.fls_uint, uint(0x0000_0001_0000_0000), 32}, + {.fls_uint, uint(0xffff_ffff_ffff_ffff), 63}, + } + + for test in Tests { + switch test.op { + case .ffs: + res := tlsf.ffs(test.v.?) + testing.expectf(t, res == test.exp, "Expected tlsf.ffs(0x%08x) == %v, got %v", test.v, test.exp, res) + case .fls: + res := tlsf.fls(test.v.?) + testing.expectf(t, res == test.exp, "Expected tlsf.fls(0x%08x) == %v, got %v", test.v, test.exp, res) + case .fls_uint: + res := tlsf.fls_uint(test.v.?) + testing.expectf(t, res == test.exp, "Expected tlsf.fls_uint(0x%16x) == %v, got %v", test.v, test.exp, res) + } + } +} \ No newline at end of file diff --git a/tests/core/mem/test_mem_dynamic_pool.odin b/tests/core/mem/test_mem_dynamic_pool.odin new file mode 100644 index 000000000..80c973c68 --- /dev/null +++ b/tests/core/mem/test_mem_dynamic_pool.odin @@ -0,0 +1,80 @@ +package test_core_mem + +import "core:testing" +import "core:mem" + + +expect_pool_allocation :: proc(t: ^testing.T, expected_used_bytes, num_bytes, alignment: int) { + pool: mem.Dynamic_Pool + mem.dynamic_pool_init(pool = &pool, alignment = alignment) + pool_allocator := mem.dynamic_pool_allocator(&pool) + + element, err := mem.alloc(num_bytes, alignment, pool_allocator) + testing.expect(t, err == .None) + testing.expect(t, element != nil) + + expected_bytes_left := pool.block_size - expected_used_bytes + testing.expectf(t, pool.bytes_left == expected_bytes_left, + ` + Allocated data with size %v bytes, expected %v bytes left, got %v bytes left, off by %v bytes. + Pool: + block_size = %v + out_band_size = %v + alignment = %v + unused_blocks = %v + used_blocks = %v + out_band_allocations = %v + current_block = %v + current_pos = %v + bytes_left = %v + `, + num_bytes, expected_bytes_left, pool.bytes_left, expected_bytes_left - pool.bytes_left, + pool.block_size, + pool.out_band_size, + pool.alignment, + pool.unused_blocks, + pool.used_blocks, + pool.out_band_allocations, + pool.current_block, + pool.current_pos, + pool.bytes_left, + ) + + mem.dynamic_pool_destroy(&pool) + testing.expect(t, pool.used_blocks == nil) +} + +expect_pool_allocation_out_of_band :: proc(t: ^testing.T, num_bytes, out_band_size: int) { + testing.expect(t, num_bytes >= out_band_size, "Sanity check failed, your test call is flawed! Make sure that num_bytes >= out_band_size!") + + pool: mem.Dynamic_Pool + mem.dynamic_pool_init(pool = &pool, out_band_size = out_band_size) + pool_allocator := mem.dynamic_pool_allocator(&pool) + + element, err := mem.alloc(num_bytes, allocator = pool_allocator) + testing.expect(t, err == .None) + testing.expect(t, element != nil) + testing.expectf(t, pool.out_band_allocations != nil, + "Allocated data with size %v bytes, which is >= out_of_band_size and it should be in pool.out_band_allocations, but isn't!", + ) + + mem.dynamic_pool_destroy(&pool) + testing.expect(t, pool.out_band_allocations == nil) +} + +@(test) +test_dynamic_pool_alloc_aligned :: proc(t: ^testing.T) { + expect_pool_allocation(t, expected_used_bytes = 16, num_bytes = 16, alignment=8) +} + +@(test) +test_dynamic_pool_alloc_unaligned :: proc(t: ^testing.T) { + expect_pool_allocation(t, expected_used_bytes = 8, num_bytes=1, alignment=8) + expect_pool_allocation(t, expected_used_bytes = 16, num_bytes=9, alignment=8) +} + +@(test) +test_dynamic_pool_alloc_out_of_band :: proc(t: ^testing.T) { + expect_pool_allocation_out_of_band(t, num_bytes = 128, out_band_size = 128) + expect_pool_allocation_out_of_band(t, num_bytes = 129, out_band_size = 128) +} \ No newline at end of file diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index 9df03414c..a8f41082a 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -8,74 +8,17 @@ A test suite for `core:net` */ +//+build !netbsd !freebsd !openbsd package test_core_net import "core:testing" -import "core:mem" -import "core:fmt" import "core:net" import "core:strconv" import "core:sync" import "core:time" import "core:thread" -import "core:os" - -_, _ :: time, thread - -TEST_count := 0 -TEST_fail := 0 - -t := &testing.T{} - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -_tracking_allocator := mem.Tracking_Allocator{} - -print_tracking_allocator_report :: proc() { - for _, leak in _tracking_allocator.allocation_map { - fmt.printf("%v leaked %v bytes\n", leak.location, leak.size) - } - - for bf in _tracking_allocator.bad_free_array { - fmt.printf("%v allocation %p was freed badly\n", bf.location, bf.memory) - } -} - -main :: proc() { - mem.tracking_allocator_init(&_tracking_allocator, context.allocator) - context.allocator = mem.tracking_allocator(&_tracking_allocator) - - address_parsing_test(t) - - tcp_tests(t) - - split_url_test(t) - join_url_test(t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - - print_tracking_allocator_report() - - if TEST_fail > 0 { - os.exit(1) - } -} +import "core:fmt" +import "core:log" @test address_parsing_test :: proc(t: ^testing.T) { @@ -89,127 +32,66 @@ address_parsing_test :: proc(t: ^testing.T) { } valid := len(vector.binstr) > 0 - - fmt.printf("%v %v\n", kind, vector.input) - - msg := "-set a proper message-" switch vector.family { case .IP4, .IP4_Alt: - /* - Does `net.parse_ip4_address` think we parsed the address properly? - */ + // Does `net.parse_ip4_address` think we parsed the address properly? non_decimal := vector.family == .IP4_Alt + any_addr := net.parse_address(vector.input, non_decimal) + parsed_ok := any_addr != nil + parsed: net.IP4_Address - any_addr := net.parse_address(vector.input, non_decimal) - parsed_ok := any_addr != nil - parsed: net.IP4_Address - - /* - Ensure that `parse_address` doesn't parse IPv4 addresses into IPv6 addreses by mistake. - */ + // Ensure that `parse_address` doesn't parse IPv4 addresses into IPv6 addreses by mistake. switch addr in any_addr { case net.IP4_Address: parsed = addr case net.IP6_Address: parsed_ok = false - msg = fmt.tprintf("parse_address mistook %v as IPv6 address %04x", vector.input, addr) - expect(t, false, msg) + testing.expectf(t, false, "parse_address mistook %v as IPv6 address %04x", vector.input, addr) } if !parsed_ok && valid { - msg = fmt.tprintf("parse_ip4_address failed to parse %v, expected %v", vector.input, binstr_to_address(vector.binstr)) + testing.expectf(t, parsed_ok == valid, "parse_ip4_address failed to parse %v, expected %v", vector.input, binstr_to_address(t, vector.binstr)) } else if parsed_ok && !valid { - msg = fmt.tprintf("parse_ip4_address parsed %v into %v, expected failure", vector.input, parsed) + testing.expectf(t, parsed_ok == valid, "parse_ip4_address parsed %v into %v, expected failure", vector.input, parsed) } - expect(t, parsed_ok == valid, msg) if valid && parsed_ok { actual_binary := address_to_binstr(parsed) - msg = fmt.tprintf("parse_ip4_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) - expect(t, actual_binary == vector.binstr, msg) + testing.expectf(t, actual_binary == vector.binstr, "parse_ip4_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) - /* - Do we turn an address back into the same string properly? - No point in testing the roundtrip if the first part failed. - */ + // Do we turn an address back into the same string properly? No point in testing the roundtrip if the first part failed. if len(vector.output) > 0 && actual_binary == vector.binstr { stringified := net.address_to_string(parsed) - msg = fmt.tprintf("address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) - expect(t, stringified == vector.output, msg) + testing.expectf(t, stringified == vector.output, "address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) } } case .IP6: - /* - Do we parse the address properly? - */ + // Do we parse the address properly? parsed, parsed_ok := net.parse_ip6_address(vector.input) if !parsed_ok && valid { - msg = fmt.tprintf("parse_ip6_address failed to parse %v, expected %04x", vector.input, binstr_to_address(vector.binstr)) + testing.expectf(t, parsed_ok == valid, "parse_ip6_address failed to parse %v, expected %04x", vector.input, binstr_to_address(t, vector.binstr)) } else if parsed_ok && !valid { - msg = fmt.tprintf("parse_ip6_address parsed %v into %04x, expected failure", vector.input, parsed) + testing.expectf(t, parsed_ok == valid, "parse_ip6_address parsed %v into %04x, expected failure", vector.input, parsed) } - expect(t, parsed_ok == valid, msg) if valid && parsed_ok { actual_binary := address_to_binstr(parsed) - msg = fmt.tprintf("parse_ip6_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) - expect(t, actual_binary == vector.binstr, msg) + testing.expectf(t, actual_binary == vector.binstr, "parse_ip6_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) - /* - Do we turn an address back into the same string properly? - No point in testing the roundtrip if the first part failed. - */ + // Do we turn an address back into the same string properly? No point in testing the roundtrip if the first part failed. if len(vector.output) > 0 && actual_binary == vector.binstr { stringified := net.address_to_string(parsed) - msg = fmt.tprintf("address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) - expect(t, stringified == vector.output, msg) + testing.expectf(t, stringified == vector.output, "address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) } } } } } -address_to_binstr :: proc(address: net.Address) -> (binstr: string) { - switch t in address { - case net.IP4_Address: - b := transmute(u32be)t - return fmt.tprintf("%08x", b) - case net.IP6_Address: - b := transmute(u128be)t - return fmt.tprintf("%32x", b) - case: - return "" - } - unreachable() -} - -binstr_to_address :: proc(binstr: string) -> (address: net.Address) { - switch len(binstr) { - case 8: // IPv4 - a, ok := strconv.parse_u64_of_base(binstr, 16) - expect(t, ok, "failed to parse test case bin string") - - ipv4 := u32be(a) - return net.IP4_Address(transmute([4]u8)ipv4) - - - case 32: // IPv6 - a, ok := strconv.parse_u128_of_base(binstr, 16) - expect(t, ok, "failed to parse test case bin string") - - ipv4 := u128be(a) - return net.IP6_Address(transmute([8]u16be)ipv4) - - case 0: - return nil - } - panic("Invalid test case") -} - Kind :: enum { IP4, // Decimal IPv4 IP4_Alt, // Non-decimal address @@ -223,10 +105,7 @@ IP_Address_Parsing_Test_Vector :: struct { // Input address to try and parse. input: string, - /* - Hexadecimal representation of the expected numeric value of the address. - Zero length means input is invalid and the parser should report failure. - */ + // Hexadecimal representation of the expected numeric value of the address. Zero length means input is invalid and the parser should report failure. binstr: string, // Expected `address_to_string` output, if a valid input and this string is non-empty. @@ -335,38 +214,32 @@ IP_Address_Parsing_Test_Vectors :: []IP_Address_Parsing_Test_Vector{ { .IP6, "c0a8", "", ""}, } -tcp_tests :: proc(t: ^testing.T) { - fmt.println("Testing two servers trying to bind to the same endpoint...") - two_servers_binding_same_endpoint(t) - fmt.println("Testing client connecting to a closed port...") - client_connects_to_closed_port(t) - fmt.println("Testing client sending server data...") - client_sends_server_data(t) -} - -ENDPOINT := net.Endpoint{ - net.IP4_Address{127, 0, 0, 1}, - 9999, -} +ENDPOINT_TWO_SERVERS := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9991} +ENDPOINT_CLOSED_PORT := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9992} +ENDPOINT_SERVER_SENDS := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9993} +ENDPOINT_UDP_ECHO := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9994} +ENDPOINT_NONBLOCK := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9995} @(test) two_servers_binding_same_endpoint :: proc(t: ^testing.T) { - skt1, err1 := net.listen_tcp(ENDPOINT) + skt1, err1 := net.listen_tcp(ENDPOINT_TWO_SERVERS) defer net.close(skt1) - skt2, err2 := net.listen_tcp(ENDPOINT) + skt2, err2 := net.listen_tcp(ENDPOINT_TWO_SERVERS) defer net.close(skt2) - expect(t, err1 == nil, "expected first server binding to endpoint to do so without error") - expect(t, err2 == net.Bind_Error.Address_In_Use, "expected second server to bind to an endpoint to return .Address_In_Use") + testing.expect(t, err1 == nil, "expected first server binding to endpoint to do so without error") + testing.expect(t, err2 == net.Bind_Error.Address_In_Use, "expected second server to bind to an endpoint to return .Address_In_Use") } @(test) client_connects_to_closed_port :: proc(t: ^testing.T) { - skt, err := net.dial_tcp(ENDPOINT) + + skt, err := net.dial_tcp(ENDPOINT_CLOSED_PORT) defer net.close(skt) - expect(t, err == net.Dial_Error.Refused, "expected dial of a closed endpoint to return .Refused") + testing.expect(t, err == net.Dial_Error.Refused, "expected dial of a closed endpoint to return .Refused") } + @(test) client_sends_server_data :: proc(t: ^testing.T) { CONTENT: string: "Hellope!" @@ -386,12 +259,12 @@ client_sends_server_data :: proc(t: ^testing.T) { } tcp_client :: proc(thread_data: rawptr) { - r := transmute(^Thread_Data)thread_data + r := cast(^Thread_Data)thread_data defer sync.wait_group_done(r.wg) - if r.skt, r.err = net.dial_tcp(ENDPOINT); r.err != nil { - log(r.t, r.err) + if r.skt, r.err = net.dial_tcp(ENDPOINT_SERVER_SENDS); r.err != nil { + testing.expectf(r.t, false, "[tcp_client:dial_tcp] %v", r.err) return } @@ -401,23 +274,21 @@ client_sends_server_data :: proc(t: ^testing.T) { } tcp_server :: proc(thread_data: rawptr) { - r := transmute(^Thread_Data)thread_data + r := cast(^Thread_Data)thread_data defer sync.wait_group_done(r.wg) - log(r.t, "tcp_server listen") - if r.skt, r.err = net.listen_tcp(ENDPOINT); r.err != nil { + if r.skt, r.err = net.listen_tcp(ENDPOINT_SERVER_SENDS); r.err != nil { sync.wait_group_done(r.wg) - log(r.t, r.err) + testing.expectf(r.t, false, "[tcp_server:listen_tcp] %v", r.err) return } sync.wait_group_done(r.wg) - log(r.t, "tcp_server accept") client: net.TCP_Socket if client, _, r.err = net.accept_tcp(r.skt.(net.TCP_Socket)); r.err != nil { - log(r.t, r.err) + testing.expectf(r.t, false, "[tcp_server:accept_tcp] %v", r.err) return } defer net.close(client) @@ -437,10 +308,7 @@ client_sends_server_data :: proc(t: ^testing.T) { thread_data[0].wg = &wg thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context) - log(t, "waiting for server to start listening") sync.wait_group_wait(&wg) - log(t, "starting up client") - sync.wait_group_add(&wg, 2) thread_data[1].t = t @@ -454,20 +322,15 @@ client_sends_server_data :: proc(t: ^testing.T) { net.close(thread_data[1].skt) thread.destroy(thread_data[1].tid) } - - log(t, "waiting for threads to finish") sync.wait_group_wait(&wg) - log(t, "threads finished") okay := thread_data[0].err == nil && thread_data[1].err == nil - msg := fmt.tprintf("Expected client and server to return `nil`, got %v and %v", thread_data[0].err, thread_data[1].err) - expect(t, okay, msg) + testing.expectf(t, okay, "Expected client and server to return `nil`, got %v and %v", thread_data[0].err, thread_data[1].err) received := string(thread_data[0].data[:thread_data[0].length]) okay = received == CONTENT - msg = fmt.tprintf("Expected client to send \"{}\", got \"{}\"", CONTENT, received) - expect(t, okay, msg) + testing.expectf(t, okay, "Expected client to send \"{}\", got \"{}\"", CONTENT, received) } URL_Test :: struct { @@ -559,22 +422,15 @@ split_url_test :: proc(t: ^testing.T) { delete(test.queries) } - msg := fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.scheme, scheme) - expect(t, scheme == test.scheme, msg) - msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.host, host) - expect(t, host == test.host, msg) - msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.path, path) - expect(t, path == test.path, msg) - msg = fmt.tprintf("Expected `net.split_url` to return %d queries, got %d queries", len(test.queries), len(queries)) - expect(t, len(queries) == len(test.queries), msg) + testing.expectf(t, scheme == test.scheme, "Expected `net.split_url` to return %s, got %s", test.scheme, scheme) + testing.expectf(t, host == test.host, "Expected `net.split_url` to return %s, got %s", test.host, host) + testing.expectf(t, path == test.path, "Expected `net.split_url` to return %s, got %s", test.path, path) + testing.expectf(t, len(queries) == len(test.queries), "Expected `net.split_url` to return %d queries, got %d queries", len(test.queries), len(queries)) for k, v in queries { expected := test.queries[k] - msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", expected, v) - expect(t, v == expected, msg) + testing.expectf(t, v == expected, "Expected `net.split_url` to return %s, got %s", expected, v) } - msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.fragment, fragment) - expect(t, fragment == test.fragment, msg) - + testing.expectf(t, fragment == test.fragment, "Expected `net.split_url` to return %s, got %s", test.fragment, fragment) } } @@ -659,7 +515,141 @@ join_url_test :: proc(t: ^testing.T) { for test_url in test.url { pass |= url == test_url } - msg := fmt.tprintf("Expected `net.join_url` to return one of %s, got %s", test.url, url) - expect(t, pass, msg) + testing.expectf(t, pass, "Expected `net.join_url` to return one of %s, got %s", test.url, url) } } + +@test +test_udp_echo :: proc(t: ^testing.T) { + server, make_server_err := net.make_unbound_udp_socket(.IP4) + if !testing.expect_value(t, make_server_err, nil) { + return + } + defer net.close(server) + + bind_server_err := net.bind(server, ENDPOINT_UDP_ECHO) + if !testing.expect_value(t, bind_server_err, nil) { + return + } + + client, make_client_err := net.make_unbound_udp_socket(.IP4) + if !testing.expect_value(t, make_client_err, nil) { + return + } + defer net.close(client) + + msg := "Hellope world!" + buf: [64]u8 + + bytes_written, send_err := net.send_udp(client, transmute([]u8)msg[:], ENDPOINT_UDP_ECHO) + if !testing.expect_value(t, send_err, nil) { + return + } + if !testing.expect_value(t, bytes_written, len(msg)) { + return + } + + bytes_read, _, read_err := net.recv_udp(server, buf[:]) + if !testing.expect_value(t, read_err, nil) { + return + } + if !testing.expect_value(t, bytes_read, len(msg)) { + return + } + + testing.expect_value(t, msg, transmute(string)buf[:bytes_read]) +} + +@test +test_dns_resolve :: proc(t: ^testing.T) { + // NOTE: This test depends on external factors, so if it fails, an IP + // address may have changed or become unavailable. + + // The net API returns only one address per protocol version, and DNS + // records can store many, so we'll have to check all possibilities. + ep4, ep6, resolve_err := net.resolve("dns.quad9.net") + if !testing.expect_value(t, resolve_err, nil) { + return + } + + ip4, ip4_ok := ep4.address.(net.IP4_Address) + if !testing.expect(t, ip4_ok, "Unable to resolve IP4") { + return + } + + valid_ip4_a := net.IP4_Address{ 9, 9, 9, 9} + valid_ip4_b := net.IP4_Address{149, 112, 112, 112} + if ip4 != valid_ip4_a && ip4 != valid_ip4_b { + log.errorf("DNS resolved to invalid IP4: %v, expected %v or %v", ip4, valid_ip4_a, valid_ip4_b) + } + + ip6, ip6_ok := ep6.address.(net.IP6_Address) + if !testing.expect(t, ip6_ok, "Unable to resolve IP6") { + return + } + + valid_ip6_a := net.IP6_Address{0x2620, 0xfe, 0, 0, 0, 0, 0, 9} + valid_ip6_b := net.IP6_Address{0x2620, 0xfe, 0, 0, 0, 0, 0, 0xfe} + if ip6 != valid_ip6_a && ip6 != valid_ip6_b { + log.errorf("DNS resolved to invalid IP6: %v, expected %v or %v", ip6, valid_ip6_a, valid_ip6_b) + } +} + +@test +test_nonblocking_option :: proc(t: ^testing.T) { + server, listen_err := net.listen_tcp(ENDPOINT_NONBLOCK) + if !testing.expect_value(t, listen_err, nil) { + return + } + defer net.close(server) + + testing.set_fail_timeout(t, 2 * time.Second) + + // If the nonblocking option isn't set correctly in the operating system, + // this should block until the timeout hits. + net.set_blocking(server, false) + + _, _, accept_err := net.accept_tcp(server) + if !testing.expect_value(t, accept_err, net.Accept_Error.Would_Block) { + return + } +} + +@(private) +address_to_binstr :: proc(address: net.Address) -> (binstr: string) { + switch t in address { + case net.IP4_Address: + b := transmute(u32be)t + return fmt.tprintf("%08x", b) + case net.IP6_Address: + b := transmute(u128be)t + return fmt.tprintf("%32x", b) + case: + return "" + } + unreachable() +} + +@(private) +binstr_to_address :: proc(t: ^testing.T, binstr: string) -> (address: net.Address) { + switch len(binstr) { + case 8: // IPv4 + a, ok := strconv.parse_u64_of_base(binstr, 16) + testing.expect(t, ok, "failed to parse test case bin string") + + ipv4 := u32be(a) + return net.IP4_Address(transmute([4]u8)ipv4) + + + case 32: // IPv6 + a, ok := strconv.parse_u128_of_base(binstr, 16) + testing.expect(t, ok, "failed to parse test case bin string") + + ipv4 := u128be(a) + return net.IP6_Address(transmute([8]u16be)ipv4) + + case 0: + return nil + } + panic("Invalid test case") +} diff --git a/tests/core/normal.odin b/tests/core/normal.odin new file mode 100644 index 000000000..065090be3 --- /dev/null +++ b/tests/core/normal.odin @@ -0,0 +1,43 @@ +package tests_core + +import rlibc "core:c/libc" + +@(init) +download_assets :: proc() { + if rlibc.system("python3 " + ODIN_ROOT + "tests/core/download_assets.py " + ODIN_ROOT + "tests/core/assets") != 0 { + panic("downloading test assets failed!") + } +} + +@(require) import "c/libc" +@(require) import "compress" +@(require) import "container" +@(require) import "encoding/base64" +@(require) import "encoding/cbor" +@(require) import "encoding/hex" +@(require) import "encoding/hxa" +@(require) import "encoding/json" +@(require) import "encoding/uuid" +@(require) import "encoding/varint" +@(require) import "encoding/xml" +@(require) import "flags" +@(require) import "fmt" +@(require) import "math" +@(require) import "math/big" +@(require) import "math/linalg/glsl" +@(require) import "math/noise" +@(require) import "math/rand" +@(require) import "mem" +@(require) import "net" +@(require) import "odin" +@(require) import "path/filepath" +@(require) import "reflect" +@(require) import "runtime" +@(require) import "slice" +@(require) import "strconv" +@(require) import "strings" +@(require) import "text/i18n" +@(require) import "text/match" +@(require) import "thread" +@(require) import "time" +@(require) import "unicode" diff --git a/tests/core/odin/test_parser.odin b/tests/core/odin/test_parser.odin index 08f73a732..772ae5982 100644 --- a/tests/core/odin/test_parser.odin +++ b/tests/core/odin/test_parser.odin @@ -1,60 +1,29 @@ package test_core_odin_parser -import "core:fmt" import "core:odin/ast" import "core:odin/parser" -import "core:odin/printer" -import "core:os" -import "core:strings" +import "base:runtime" import "core:testing" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_parse_demo(&t) - test_parse_bitfield(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - - @test test_parse_demo :: proc(t: ^testing.T) { - pkg, ok := parser.parse_package_from_path("examples/demo") + context.allocator = context.temp_allocator + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + pkg, ok := parser.parse_package_from_path(ODIN_ROOT + "examples/demo") - expect(t, ok == true, "parser.parse_package_from_path failed") + testing.expect(t, ok, "parser.parse_package_from_path failed") for key, value in pkg.files { - expect(t, value.syntax_error_count == 0, fmt.tprintf("%v should contain zero errors", key)) + testing.expectf(t, value.syntax_error_count == 0, "%v should contain zero errors", key) } } @test test_parse_bitfield :: proc(t: ^testing.T) { + context.allocator = context.temp_allocator + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + file := ast.File{ fullpath = "test.odin", src = ` @@ -80,15 +49,5 @@ Foo :: bit_field uint { p := parser.default_parser() ok := parser.parse_file(&p, &file) - expect(t, ok == true, "bad parse") - - cfg := printer.default_style - cfg.newline_style = .LF - print := printer.make_printer(cfg) - out := printer.print(&print, &file) - - tsrc := strings.trim_space(file.src) - tout := strings.trim_space(out) - - expect(t, tsrc == tout, fmt.tprintf("\n%s\n!=\n%s", tsrc, tout)) -} + testing.expect(t, ok, "bad parse") +} \ No newline at end of file diff --git a/tests/core/os/test_core_os_exit.odin b/tests/core/os/test_core_os_exit.odin deleted file mode 100644 index 2ab274f5e..000000000 --- a/tests/core/os/test_core_os_exit.odin +++ /dev/null @@ -1,10 +0,0 @@ -// Tests that Odin run returns exit code of built executable on Unix -// Needs exit status to be inverted to return 0 on success, e.g. -// $(./odin run tests/core/os/test_core_os_exit.odin && exit 1 || exit 0) -package test_core_os_exit - -import "core:os" - -main :: proc() { - os.exit(1) -} diff --git a/tests/core/path/filepath/test_core_filepath.odin b/tests/core/path/filepath/test_core_filepath.odin index 4c70e5f28..f0137f69b 100644 --- a/tests/core/path/filepath/test_core_filepath.odin +++ b/tests/core/path/filepath/test_core_filepath.odin @@ -1,26 +1,19 @@ // Tests "path.odin" in "core:path/filepath". -// Must be run with `-collection:tests=` flag, e.g. -// ./odin run tests/core/path/filepath/test_core_filepath.odin -collection:tests=tests package test_core_filepath import "core:fmt" import "core:path/filepath" import "core:testing" -import tc "tests:common" - -main :: proc() { - t := testing.T{} +@(test) +test_split_list :: proc(t: ^testing.T) { when ODIN_OS == .Windows { - test_split_list_windows(&t) + test_split_list_windows(t) } else { - test_split_list_unix(&t) + test_split_list_unix(t) } - - tc.report(&t) } -@test test_split_list_windows :: proc(t: ^testing.T) { Datum :: struct { i: int, @@ -41,13 +34,11 @@ test_split_list_windows :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i)) r := filepath.split_list(d.v) - defer delete(r) - tc.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", - i, #procedure, d.v, len(r), len(d.e))) + defer delete_split(r) + testing.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", i, #procedure, d.v, len(r), len(d.e))) if len(r) == len(d.e) { for _, j in r { - tc.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v", - i, #procedure, d.v, r[j], j, d.e[j])) + testing.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v", i, #procedure, d.v, r[j], j, d.e[j])) } } } @@ -55,47 +46,43 @@ test_split_list_windows :: proc(t: ^testing.T) { { v := "" r := filepath.split_list(v) - tc.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r)) + defer delete_split(r) + testing.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r)) } { v := "a" r := filepath.split_list(v) - defer delete(r) - tc.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r))) + defer delete_split(r) + testing.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r))) if len(r) == 1 { - tc.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0])) + testing.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0])) } } } -@test test_split_list_unix :: proc(t: ^testing.T) { Datum :: struct { - i: int, v: string, e: [3]string, } @static data := []Datum{ - { 0, "/opt/butler:/home/fancykillerpanda/Projects/Odin/Odin:/usr/local/sbin", + { "/opt/butler:/home/fancykillerpanda/Projects/Odin/Odin:/usr/local/sbin", [3]string{"/opt/butler", "/home/fancykillerpanda/Projects/Odin/Odin", "/usr/local/sbin"} }, // Issue #1537 - { 1, "a::b", [3]string{"a", "", "b"} }, - { 2, "a:b:", [3]string{"a", "b", ""} }, - { 3, ":a:b", [3]string{"", "a", "b"} }, - { 4, "::", [3]string{"", "", ""} }, - { 5, "\"a:b\"c:d:\"f\"", [3]string{"a:bc", "d", "f"} }, - { 6, "\"a:b:c\":d\":e\":f", [3]string{"a:b:c", "d:e", "f"} }, + { "a::b", [3]string{"a", "", "b"} }, + { "a:b:", [3]string{"a", "b", ""} }, + { ":a:b", [3]string{"", "a", "b"} }, + { "::", [3]string{"", "", ""} }, + { "\"a:b\"c:d:\"f\"", [3]string{"a:bc", "d", "f"} }, + { "\"a:b:c\":d\":e\":f", [3]string{"a:b:c", "d:e", "f"} }, } - for d, i in data { - assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i)) + for d in data { r := filepath.split_list(d.v) - defer delete(r) - tc.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", - i, #procedure, d.v, len(r), len(d.e))) + defer delete_split(r) + testing.expectf(t, len(r) == len(d.e), "%s len(r) %d != len(d.e) %d", d.v, len(r), len(d.e)) if len(r) == len(d.e) { for _, j in r { - tc.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v", - i, #procedure, d.v, r[j], j, d.e[j])) + testing.expectf(t, r[j] == d.e[j], "%v -> %v[%d] != %v", d.v, r[j], j, d.e[j]) } } } @@ -103,15 +90,23 @@ test_split_list_unix :: proc(t: ^testing.T) { { v := "" r := filepath.split_list(v) - tc.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r)) + testing.expectf(t, r == nil, "'%s' -> '%v' != nil", v, r) } { v := "a" r := filepath.split_list(v) - defer delete(r) - tc.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r))) + defer delete_split(r) + testing.expectf(t, len(r) == 1, "'%s' len(r) %d != 1", v, len(r)) if len(r) == 1 { - tc.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0])) + testing.expectf(t, r[0] == "a", "'%v' -> %v[0] != a", v, r[0]) } } } + +@(private) +delete_split :: proc(s: []string) { + for part in s { + delete(part) + } + delete(s) +} \ No newline at end of file diff --git a/tests/core/reflect/test_core_reflect.odin b/tests/core/reflect/test_core_reflect.odin index a3a66f968..7d2394688 100644 --- a/tests/core/reflect/test_core_reflect.odin +++ b/tests/core/reflect/test_core_reflect.odin @@ -1,21 +1,8 @@ // Tests "core:reflect/reflect". -// Must be run with `-collection:tests=` flag, e.g. -// ./odin run tests/core/reflect/test_core_reflect.odin -out=tests/core/test_core_reflect -collection:tests=./tests package test_core_reflect -import "core:fmt" import "core:reflect" import "core:testing" -import tc "tests:common" - -main :: proc() { - t := testing.T{} - - test_as_u64(&t) - test_as_f64(&t) - - tc.report(&t) -} @test test_as_u64 :: proc(t: ^testing.T) { @@ -31,9 +18,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i8 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i8 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i8 %v !valid", d.v) + testing.expectf(t, r == d.e, "i8 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -48,9 +34,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i16 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i16 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i16 %v !valid", d.v) + testing.expectf(t, r == d.e, "i16 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -65,9 +50,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i32 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i32 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i32 %v !valid", d.v) + testing.expectf(t, r == d.e, "i32 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -82,9 +66,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i64 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i64 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i64 %v !valid", d.v) + testing.expectf(t, r == d.e, "i64 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -102,9 +85,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i128 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i128 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i128 %v !valid", d.v) + testing.expectf(t, r == d.e, "i128 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -118,8 +100,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f16 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f16 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "f16 %v !valid", d.v) + testing.expectf(t, r == d.e, "f16 %v -> %v != %v", d.v, r, d.e) } } { @@ -132,8 +114,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f32 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f32 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "f32 %v !valid", d.v) + testing.expectf(t, r == d.e, "f32 %v -> %v != %v", d.v, r, d.e) } } { @@ -146,8 +128,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f64 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "f64 %v !valid", d.v) + testing.expectf(t, r == d.e, "f64 %v -> %v != %v", d.v, r, d.e) } } } @@ -166,8 +148,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i8 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i8 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "i8 %v !valid", d.v) + testing.expectf(t, r == d.e, "i8 %v -> %v != %v", d.v, r, d.e) } } { @@ -182,8 +164,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i16 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i16 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "i16 %v !valid", d.v) + testing.expectf(t, r == d.e, "i16 %v -> %v != %v", d.v, r, d.e) } } { @@ -198,8 +180,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i32 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i32 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "i32 %v !valid", d.v) + testing.expectf(t, r == d.e, "i32 %v -> %v != %v", d.v, r, d.e) } } { @@ -214,8 +196,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i64 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "i64 %v !valid", d.v) + testing.expectf(t, r == d.e, "i64 %v -> %v != %v", d.v, r, d.e) } } { @@ -231,9 +213,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i128 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i128 %v) -> %v (%H) != %v (%H)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i128 %v !valid", d.v) + testing.expectf(t, r == d.e, "i128 %v -> %v (%H) != %v (%H)", d.v, r, r, d.e, d.e) } } { @@ -247,9 +228,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f16 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f16 %v (%H)) -> %v (%H) != %v (%H)\n", - i, #procedure, d.v, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "f16 %v !valid", d.v) + testing.expectf(t, r == d.e, "f16 %v (%H) -> %v (%H) != %v (%H)", d.v, d.v, r, r, d.e, d.e) } } { @@ -262,9 +242,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f32 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f32 %v (%H)) -> %v (%H) != %v (%H)\n", - i, #procedure, d.v, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "f32 %v !valid", d.v) + testing.expectf(t, r == d.e, "f32 %v (%H) -> %v (%H) != %v (%H)", d.v, d.v, r, r, d.e, d.e) } } { @@ -277,8 +256,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f64 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "f64 %v !valid", d.v) + testing.expectf(t, r == d.e, "f64 %v -> %v != %v", d.v, r, d.e) } } -} +} \ No newline at end of file diff --git a/tests/core/runtime/test_core_runtime.odin b/tests/core/runtime/test_core_runtime.odin index 786cf003a..008146dcf 100644 --- a/tests/core/runtime/test_core_runtime.odin +++ b/tests/core/runtime/test_core_runtime.odin @@ -1,43 +1,10 @@ package test_core_runtime -import "core:fmt" import "base:intrinsics" import "core:mem" -import "core:os" -import "core:reflect" import "base:runtime" import "core:testing" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect_value :: testing.expect_value -} else { - expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { - TEST_count += 1 - ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) - if !ok { - TEST_fail += 1 - fmt.printf("[%v] expected %v, got %v\n", loc, expected, value) - } - return ok - } -} - -main :: proc() { - t := testing.T{} - - test_temp_allocator_big_alloc_and_alignment(&t) - test_temp_allocator_alignment_boundary(&t) - test_temp_allocator_returns_correct_size(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - // Tests that having space for the allocation, but not for the allocation and alignment // is handled correctly. @(test) @@ -47,7 +14,7 @@ test_temp_allocator_alignment_boundary :: proc(t: ^testing.T) { _, _ = mem.alloc(int(runtime.DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE)-120) _, err := mem.alloc(112, 32) - expect_value(t, err, nil) + testing.expect(t, err == nil) } // Tests that big allocations with big alignments are handled correctly. @@ -58,7 +25,7 @@ test_temp_allocator_big_alloc_and_alignment :: proc(t: ^testing.T) { mappy: map[[8]int]int err := reserve(&mappy, 50000) - expect_value(t, err, nil) + testing.expect(t, err == nil) } @(test) @@ -67,6 +34,6 @@ test_temp_allocator_returns_correct_size :: proc(t: ^testing.T) { context.allocator = runtime.arena_allocator(&arena) bytes, err := mem.alloc_bytes(10, 16) - expect_value(t, err, nil) - expect_value(t, len(bytes), 10) -} + testing.expect(t, err == nil) + testing.expect(t, len(bytes) == 10) +} \ No newline at end of file diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index 06329ddda..3e249055b 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -1,56 +1,17 @@ package test_core_slice import "core:slice" -import "core:strings" import "core:testing" -import "core:fmt" -import "core:os" import "core:math/rand" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_sort_with_indices(&t) - test_binary_search(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} +import "core:log" @test test_sort_with_indices :: proc(t: ^testing.T) { - seed := rand.uint64() - fmt.printf("Random seed: %v\n", seed) - // Test sizes are all prime. test_sizes :: []int{7, 13, 347, 1031, 10111, 100003} for test_size in test_sizes { - fmt.printf("Sorting %v random u64 values along with index.\n", test_size) - - r := rand.create(seed) + rand.reset(t.seed) vals := make([]u64, test_size) r_idx := make([]int, test_size) // Reverse index @@ -61,7 +22,7 @@ test_sort_with_indices :: proc(t: ^testing.T) { // Set up test values for _, i in vals { - vals[i] = rand.uint64(&r) + vals[i] = rand.uint64() } // Sort @@ -69,7 +30,7 @@ test_sort_with_indices :: proc(t: ^testing.T) { defer delete(f_idx) // Verify sorted test values - rand.init(&r, seed) + rand.reset(t.seed) for v, i in f_idx { r_idx[v] = i @@ -79,14 +40,14 @@ test_sort_with_indices :: proc(t: ^testing.T) { for v, i in vals { if i > 0 { val_pass := v >= last - expect(t, val_pass, "Expected values to have been sorted.") + testing.expect(t, val_pass, "Expected randomized test values to have been sorted") if !val_pass { break } } - idx_pass := vals[r_idx[i]] == rand.uint64(&r) - expect(t, idx_pass, "Expected index to have been sorted.") + idx_pass := vals[r_idx[i]] == rand.uint64() + testing.expect(t, idx_pass, "Expected index to have been sorted") if !idx_pass { break } @@ -97,16 +58,11 @@ test_sort_with_indices :: proc(t: ^testing.T) { @test test_sort_by_indices :: proc(t: ^testing.T) { - seed := rand.uint64() - fmt.printf("Random seed: %v\n", seed) - // Test sizes are all prime. test_sizes :: []int{7, 13, 347, 1031, 10111, 100003} for test_size in test_sizes { - fmt.printf("Sorting %v random u64 values along with index.\n", test_size) - - r := rand.create(seed) + rand.reset(t.seed) vals := make([]u64, test_size) r_idx := make([]int, test_size) // Reverse index @@ -117,7 +73,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { // Set up test values for _, i in vals { - vals[i] = rand.uint64(&r) + vals[i] = rand.uint64() } // Sort @@ -125,7 +81,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { defer delete(f_idx) // Verify sorted test values - rand.init(&r, seed) + rand.reset(t.seed) { indices := make([]int, test_size) @@ -138,7 +94,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { defer delete(sorted_indices) for v, i in sorted_indices { idx_pass := v == f_idx[i] - expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") + testing.expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") if !idx_pass { break } @@ -154,7 +110,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { slice.sort_by_indices_overwrite(indices, f_idx) for v, i in indices { idx_pass := v == f_idx[i] - expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") + testing.expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") if !idx_pass { break } @@ -174,7 +130,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { slice.sort_by_indices(indices, swap, f_idx) for v, i in swap { idx_pass := v == f_idx[i] - expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") + testing.expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") if !idx_pass { break } @@ -185,61 +141,138 @@ test_sort_by_indices :: proc(t: ^testing.T) { @test test_binary_search :: proc(t: ^testing.T) { - builder := strings.Builder{} - defer strings.builder_destroy(&builder) - - test_search :: proc(t: ^testing.T, b: ^strings.Builder, s: []i32, v: i32) -> (int, bool) { - log(t, fmt.sbprintf(b, "Searching for %v in %v", v, s)) - strings.builder_reset(b) - index, found := slice.binary_search(s, v) - log(t, fmt.sbprintf(b, "index: %v, found: %v", index, found)) - strings.builder_reset(b ) - - return index, found - } - index: int found: bool s := []i32{0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55} - index, found = test_search(t, &builder, s, 13) - expect(t, index == 9, "Expected index to be 9.") - expect(t, found == true, "Expected found to be true.") + index, found = slice.binary_search(s, 13) + testing.expect(t, index == 9, "Expected index to be 9") + testing.expect(t, found == true, "Expected found to be true") - index, found = test_search(t, &builder, s, 4) - expect(t, index == 7, "Expected index to be 7.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(s, 4) + testing.expect(t, index == 7, "Expected index to be 7.") + testing.expect(t, found == false, "Expected found to be false.") - index, found = test_search(t, &builder, s, 100) - expect(t, index == 13, "Expected index to be 13.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(s, 100) + testing.expect(t, index == 13, "Expected index to be 13.") + testing.expect(t, found == false, "Expected found to be false.") - index, found = test_search(t, &builder, s, 1) - expect(t, index >= 1 && index <= 4, "Expected index to be 1, 2, 3, or 4.") - expect(t, found == true, "Expected found to be true.") + index, found = slice.binary_search(s, 1) + testing.expect(t, index >= 1 && index <= 4, "Expected index to be 1, 2, 3, or 4.") + testing.expect(t, found == true, "Expected found to be true.") - index, found = test_search(t, &builder, s, -1) - expect(t, index == 0, "Expected index to be 0.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(s, -1) + testing.expect(t, index == 0, "Expected index to be 0.") + testing.expect(t, found == false, "Expected found to be false.") a := []i32{} - index, found = test_search(t, &builder, a, 13) - expect(t, index == 0, "Expected index to be 0.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(a, 13) + testing.expect(t, index == 0, "Expected index to be 0.") + testing.expect(t, found == false, "Expected found to be false.") b := []i32{1} - index, found = test_search(t, &builder, b, 13) - expect(t, index == 1, "Expected index to be 1.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(b, 13) + testing.expect(t, index == 1, "Expected index to be 1.") + testing.expect(t, found == false, "Expected found to be false.") - index, found = test_search(t, &builder, b, 1) - expect(t, index == 0, "Expected index to be 0.") - expect(t, found == true, "Expected found to be true.") + index, found = slice.binary_search(b, 1) + testing.expect(t, index == 0, "Expected index to be 0.") + testing.expect(t, found == true, "Expected found to be true.") - index, found = test_search(t, &builder, b, 0) - expect(t, index == 0, "Expected index to be 0.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(b, 0) + testing.expect(t, index == 0, "Expected index to be 0.") + testing.expect(t, found == false, "Expected found to be false.") } + +@test +test_permutation_iterator :: proc(t: ^testing.T) { + // Big enough to do some sanity checking but not overly large. + FAC_5 :: 120 + s := []int{1, 2, 3, 4, 5} + seen: map[int]bool + defer delete(seen) + + iter := slice.make_permutation_iterator(s) + defer slice.destroy_permutation_iterator(iter) + + permutations_counted: int + for slice.permute(&iter) { + n := 0 + for item in s { + n *= 10 + n += item + } + if n in seen { + log.error("Permutation iterator made a duplicate permutation.") + return + } + seen[n] = true + permutations_counted += 1 + } + + testing.expect_value(t, len(seen), FAC_5) + testing.expect_value(t, permutations_counted, FAC_5) +} + +// Test inputs from #3276 and #3769 +UNIQUE_TEST_VECTORS :: [][2][]int{ + {{2,2,2}, {2}}, + {{1,1,1,2,2,3,3,3,3}, {1,2,3}}, + {{1,2,4,4,5}, {1,2,4,5}}, +} + +@test +test_unique :: proc(t: ^testing.T) { + for v in UNIQUE_TEST_VECTORS { + assorted := v[0] + expected := v[1] + + uniq := slice.unique(assorted) + testing.expectf(t, slice.equal(uniq, expected), "Expected slice.uniq(%v) == %v, got %v", v[0], v[1], uniq) + } + + for v in UNIQUE_TEST_VECTORS { + assorted := v[0] + expected := v[1] + + uniq := slice.unique_proc(assorted, proc(a, b: int) -> bool { + return a == b + }) + testing.expectf(t, slice.equal(uniq, expected), "Expected slice.unique_proc(%v, ...) == %v, got %v", v[0], v[1], uniq) + } + + r := rand.create(t.seed) + context.random_generator = rand.default_random_generator(&r) + + // 10_000 random tests + for _ in 0..<10_000 { + assorted: [dynamic]i64 + expected: [dynamic]i64 + + // Prime with 1 value + old := rand.int63() + append(&assorted, old) + append(&expected, old) + + // Add 99 additional random values + for _ in 1..<100 { + new := rand.int63() + append(&assorted, new) + if old != new { + append(&expected, new) + } + old = new + } + + original := slice.clone(assorted[:]) + uniq := slice.unique(assorted[:]) + testing.expectf(t, slice.equal(uniq, expected[:]), "Expected slice.uniq(%v) == %v, got %v", original, expected, uniq) + + delete(assorted) + delete(original) + delete(expected) + } +} \ No newline at end of file diff --git a/tests/core/speed.odin b/tests/core/speed.odin new file mode 100644 index 000000000..a4b2b6a69 --- /dev/null +++ b/tests/core/speed.odin @@ -0,0 +1,6 @@ +// Tests intended to be ran with optimizations on +package tests_core + +@(require) import "crypto" +@(require) import "hash" +@(require) import "image" \ No newline at end of file diff --git a/tests/core/strconv/test_core_strconv.odin b/tests/core/strconv/test_core_strconv.odin new file mode 100644 index 000000000..6b70654cc --- /dev/null +++ b/tests/core/strconv/test_core_strconv.odin @@ -0,0 +1,145 @@ +package test_core_strconv + +import "core:math" +import "core:strconv" +import "core:testing" + +@(test) +test_float :: proc(t: ^testing.T) { + n: int + f: f64 + ok: bool + + f, ok = strconv.parse_f64("1.2", &n) + testing.expect_value(t, f, 1.2) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, true) + + f, ok = strconv.parse_f64("1.2a", &n) + testing.expect_value(t, f, 1.2) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, false) + + f, ok = strconv.parse_f64("+", &n) + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + + f, ok = strconv.parse_f64("-", &n) + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + +} + +@(test) +test_nan :: proc(t: ^testing.T) { + n: int + f: f64 + ok: bool + + f, ok = strconv.parse_f64("nan", &n) + testing.expect_value(t, math.classify(f), math.Float_Class.NaN) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, true) + + f, ok = strconv.parse_f64("nAN", &n) + testing.expect_value(t, math.classify(f), math.Float_Class.NaN) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, true) + + f, ok = strconv.parse_f64("Nani", &n) + testing.expect_value(t, math.classify(f), math.Float_Class.NaN) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, false) +} + +@(test) +test_infinity :: proc(t: ^testing.T) { + pos_inf := math.inf_f64(+1) + neg_inf := math.inf_f64(-1) + + n: int + s := "infinity" + + for i in 0 ..< len(s) + 1 { + ss := s[:i] + f, ok := strconv.parse_f64(ss, &n) + if i >= 3 { // "inf" .. "infinity" + expected_n := 8 if i == 8 else 3 + expected_ok := i == 3 || i == 8 + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, expected_n) + testing.expect_value(t, ok, expected_ok) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) + } else { // invalid substring + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Zero) + } + } + + s = "+infinity" + for i in 0 ..< len(s) + 1 { + ss := s[:i] + f, ok := strconv.parse_f64(ss, &n) + if i >= 4 { // "+inf" .. "+infinity" + expected_n := 9 if i == 9 else 4 + expected_ok := i == 4 || i == 9 + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, expected_n) + testing.expect_value(t, ok, expected_ok) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) + } else { // invalid substring + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Zero) + } + } + + s = "-infinity" + for i in 0 ..< len(s) + 1 { + ss := s[:i] + f, ok := strconv.parse_f64(ss, &n) + if i >= 4 { // "-inf" .. "infinity" + expected_n := 9 if i == 9 else 4 + expected_ok := i == 4 || i == 9 + testing.expect_value(t, f, neg_inf) + testing.expect_value(t, n, expected_n) + testing.expect_value(t, ok, expected_ok) + testing.expect_value(t, math.classify(f), math.Float_Class.Neg_Inf) + } else { // invalid substring + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Zero) + } + } + + // Make sure odd casing works. + batch := [?]string {"INFiniTY", "iNfInItY", "InFiNiTy"} + for ss in batch { + f, ok := strconv.parse_f64(ss, &n) + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 8) + testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) + } + + // Explicitly check how trailing characters are handled. + s = "infinityyyy" + f, ok := strconv.parse_f64(s, &n) + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 8) + testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) + + s = "inflippity" + f, ok = strconv.parse_f64(s, &n) + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) +} diff --git a/tests/core/strings/test_core_strings.odin b/tests/core/strings/test_core_strings.odin index f49476765..0ee2b3eb9 100644 --- a/tests/core/strings/test_core_strings.odin +++ b/tests/core/strings/test_core_strings.odin @@ -2,81 +2,42 @@ package test_core_strings import "core:strings" import "core:testing" -import "core:fmt" -import "core:os" import "base:runtime" -import "core:mem" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_index_any_small_string_not_found(&t) - test_index_any_larger_string_not_found(&t) - test_index_any_small_string_found(&t) - test_index_any_larger_string_found(&t) - test_cut(&t) - test_case_conversion(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} @test test_index_any_small_string_not_found :: proc(t: ^testing.T) { index := strings.index_any(".", "/:\"") - expect(t, index == -1, "index_any should be negative") + testing.expect(t, index == -1, "index_any should be negative") } @test test_index_any_larger_string_not_found :: proc(t: ^testing.T) { index := strings.index_any("aaaaaaaa.aaaaaaaa", "/:\"") - expect(t, index == -1, "index_any should be negative") + testing.expect(t, index == -1, "index_any should be negative") } @test test_index_any_small_string_found :: proc(t: ^testing.T) { index := strings.index_any(".", "/:.\"") - expect(t, index == 0, "index_any should be 0") + testing.expect(t, index == 0, "index_any should be 0") } @test test_index_any_larger_string_found :: proc(t: ^testing.T) { index := strings.index_any("aaaaaaaa:aaaaaaaa", "/:\"") - expect(t, index == 8, "index_any should be 8") + testing.expect(t, index == 8, "index_any should be 8") } @test test_last_index_any_small_string_found :: proc(t: ^testing.T) { index := strings.last_index_any(".", "/:.\"") - expect(t, index == 0, "last_index_any should be 0") + testing.expect(t, index == 0, "last_index_any should be 0") } @test test_last_index_any_small_string_not_found :: proc(t: ^testing.T) { index := strings.last_index_any(".", "/:\"") - expect(t, index == -1, "last_index_any should be -1") + testing.expect(t, index == -1, "last_index_any should be -1") } Cut_Test :: struct { @@ -100,9 +61,12 @@ test_cut :: proc(t: ^testing.T) { res := strings.cut(test.input, test.offset, test.length) defer delete(res) - msg := fmt.tprintf("cut(\"%v\", %v, %v) expected to return \"%v\", got \"%v\"", - test.input, test.offset, test.length, test.output, res) - expect(t, res == test.output, msg) + testing.expectf( + t, + res == test.output, + "cut(\"%v\", %v, %v) expected to return \"%v\", got \"%v\"", + test.input, test.offset, test.length, test.output, res, + ) } } @@ -118,7 +82,7 @@ Case_Kind :: enum { Ada_Case, } -Case_Proc :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) +Case_Proc :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) test_cases := [Case_Kind]struct{s: string, p: Case_Proc}{ .Lower_Space_Case = {"hellope world", to_lower_space_case}, @@ -132,33 +96,31 @@ test_cases := [Case_Kind]struct{s: string, p: Case_Proc}{ .Ada_Case = {"Hellope_World", to_ada_case}, } -to_lower_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { +to_lower_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_delimiter_case(r, ' ', false, allocator) } -to_upper_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { +to_upper_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_delimiter_case(r, ' ', true, allocator) } // NOTE: we have these wrappers as having #optional_allocator_error changes the type to not be equivalent -to_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_snake_case(r, allocator) } -to_upper_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_upper_snake_case(r, allocator) } -to_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_kebab_case(r, allocator) } -to_upper_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_upper_kebab_case(r, allocator) } -to_camel_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_camel_case(r, allocator) } -to_pascal_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_pascal_case(r, allocator) } -to_ada_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_ada_case(r, allocator) } +to_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_snake_case(r, allocator) } +to_upper_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_upper_snake_case(r, allocator) } +to_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_kebab_case(r, allocator) } +to_upper_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_upper_kebab_case(r, allocator) } +to_camel_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_camel_case(r, allocator) } +to_pascal_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_pascal_case(r, allocator) } +to_ada_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_ada_case(r, allocator) } @test test_case_conversion :: proc(t: ^testing.T) { for entry in test_cases { for test_case, case_kind in test_cases { result, err := entry.p(test_case.s, context.allocator) - msg := fmt.tprintf("ERROR: We got the allocation error '{}'\n", err) - expect(t, err == nil, msg) + testing.expectf(t, err == nil, "ERROR: We got the allocation error '{}'\n", err) defer delete(result) - msg = fmt.tprintf("ERROR: Input `{}` to converter {} does not match `{}`, got `{}`.\n", test_case.s, case_kind, entry.s, result) - expect(t, result == entry.s, msg) + 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 diff --git a/tests/core/text/i18n/test_core_text_i18n.odin b/tests/core/text/i18n/test_core_text_i18n.odin index ec632d432..4c70bd8b9 100644 --- a/tests/core/text/i18n/test_core_text_i18n.odin +++ b/tests/core/text/i18n/test_core_text_i18n.odin @@ -1,32 +1,11 @@ package test_core_text_i18n -import "core:mem" -import "core:fmt" -import "core:os" +import "base:runtime" import "core:testing" import "core:text/i18n" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} T :: i18n.get +Tn :: i18n.get_n Test :: struct { section: string, @@ -37,125 +16,170 @@ Test :: struct { Test_Suite :: struct { file: string, - loader: proc(string, i18n.Parse_Options, proc(int) -> int, mem.Allocator) -> (^i18n.Translation, i18n.Error), + loader: proc(string, i18n.Parse_Options, proc(int) -> int, runtime.Allocator) -> (^i18n.Translation, i18n.Error), + plural: proc(int) -> int, err: i18n.Error, options: i18n.Parse_Options, tests: []Test, } -TESTS := []Test_Suite{ - { - file = "assets/I18N/nl_NL.mo", +TEST_SUITE_PATH :: ODIN_ROOT + "tests/core/assets/I18N/" + +@(test) +test_custom_pluralizer :: proc(t: ^testing.T) { + // Custom pluralizer for plur.mo + plur_mo_pluralizer :: proc(n: int) -> (slot: int) { + switch { + case n == 1: return 0 + case n != 0 && n % 1_000_000 == 0: return 1 + case: return 2 + } + } + + test(t, { + file = TEST_SUITE_PATH + "plur.mo", loader = i18n.parse_mo_file, + plural = plur_mo_pluralizer, tests = { // These are in the catalog. - { "", "There are 69,105 leaves here.", "Er zijn hier 69.105 bladeren.", 1 }, - { "", "Hellope, World!", "Hallo, Wereld!", 1 }, - { "", "There is %d leaf.\n", "Er is %d blad.\n", 1 }, - { "", "There are %d leaves.\n", "Er is %d blad.\n", 1 }, - { "", "There is %d leaf.\n", "Er zijn %d bladeren.\n", 42 }, - { "", "There are %d leaves.\n", "Er zijn %d bladeren.\n", 42 }, + {"", "Message1", "This is message 1", 1}, + {"", "Message1", "This is message 1 - plural A", 1_000_000}, + {"", "Message1", "This is message 1 - plural B", 42}, + {"", "Message1/plural", "This is message 1", 1}, + {"", "Message1/plural", "This is message 1 - plural A", 1_000_000}, + {"", "Message1/plural", "This is message 1 - plural B", 42}, + + // This isn't in the catalog, so should return the key. + {"", "Come visit us on Discord!", "Come visit us on Discord!", 1}, + }, + }) +} + +@(test) +test_mixed_context :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "mixed_context.mo", + loader = i18n.parse_mo_file, + plural = nil, + tests = { + // These are in the catalog. + {"", "Message1", "This is message 1 without Context",-1}, + {"Context", "Message1", "This is message 1 with Context", -1}, // This isn't in the catalog, so should ruturn the key. - { "", "Come visit us on Discord!", "Come visit us on Discord!", 1 }, + {"", "Come visit us on Discord!", "Come visit us on Discord!", -1}, }, - }, + }) +} - // QT Linguist with default loader options. - { - file = "assets/I18N/nl_NL-qt-ts.ts", - loader = i18n.parse_qt_linguist_file, +@(test) +test_mixed_context_dupe :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "mixed_context.mo", + loader = i18n.parse_mo_file, + plural = nil, + // Message1 exists twice, once within Context, which has been merged into "" + err = .Duplicate_Key, + options = {merge_sections = true}, + }) +} + +@(test) +test_nl_mo :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "nl_NL.mo", + loader = i18n.parse_mo_file, + plural = nil, // Default pluralizer tests = { // These are in the catalog. - { "Page", "Text for translation", "Tekst om te vertalen", 1}, - { "Page", "Also text to translate", "Ook tekst om te vertalen", 1}, - { "installscript", "99 bottles of beer on the wall", "99 flessen bier op de muur", 1}, - { "apple_count", "%d apple(s)", "%d appel", 1}, - { "apple_count", "%d apple(s)", "%d appels", 42}, + {"", "There are 69,105 leaves here.", "Er zijn hier 69.105 bladeren.", -1}, + {"", "Hellope, World!", "Hallo, Wereld!", -1}, + {"", "There is %d leaf.\n", "Er is %d blad.\n", 1}, + {"", "There are %d leaves.\n", "Er is %d blad.\n", 1}, + {"", "There is %d leaf.\n", "Er zijn %d bladeren.\n", 42}, + {"", "There are %d leaves.\n", "Er zijn %d bladeren.\n", 42}, + + // This isn't in the catalog, so should ruturn the key. + {"", "Come visit us on Discord!", "Come visit us on Discord!", -1}, + }, + }) +} + +@(test) +test_qt_linguist :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "nl_NL-qt-ts.ts", + loader = i18n.parse_qt_linguist_file, + plural = nil, // Default pluralizer + tests = { + // These are in the catalog. + {"Page", "Text for translation", "Tekst om te vertalen", -1}, + {"Page", "Also text to translate", "Ook tekst om te vertalen", -1}, + {"installscript", "99 bottles of beer on the wall", "99 flessen bier op de muur", -1}, + {"apple_count", "%d apple(s)", "%d appel", 1}, + {"apple_count", "%d apple(s)", "%d appels", 42}, // These aren't in the catalog, so should ruturn the key. - { "", "Come visit us on Discord!", "Come visit us on Discord!", 1 }, - { "Fake_Section", "Come visit us on Discord!", "Come visit us on Discord!", 1 }, + {"", "Come visit us on Discord!", "Come visit us on Discord!", -1}, + {"Fake_Section", "Come visit us on Discord!", "Come visit us on Discord!", -1}, }, - }, + }) +} - // QT Linguist, merging sections. - { - file = "assets/I18N/nl_NL-qt-ts.ts", +@(test) +test_qt_linguist_merge_sections :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "nl_NL-qt-ts.ts", loader = i18n.parse_qt_linguist_file, + plural = nil, // Default pluralizer options = {merge_sections = true}, tests = { // All of them are now in section "", lookup with original section should return the key. - { "", "Text for translation", "Tekst om te vertalen", 1}, - { "", "Also text to translate", "Ook tekst om te vertalen", 1}, - { "", "99 bottles of beer on the wall", "99 flessen bier op de muur", 1}, - { "", "%d apple(s)", "%d appel", 1}, - { "", "%d apple(s)", "%d appels", 42}, + {"", "Text for translation", "Tekst om te vertalen", -1}, + {"", "Also text to translate", "Ook tekst om te vertalen", -1}, + {"", "99 bottles of beer on the wall", "99 flessen bier op de muur", -1}, + {"", "%d apple(s)", "%d appel", 1}, + {"", "%d apple(s)", "%d appels", 42}, // All of them are now in section "", lookup with original section should return the key. - { "Page", "Text for translation", "Text for translation", 1}, - { "Page", "Also text to translate", "Also text to translate", 1}, - { "installscript", "99 bottles of beer on the wall", "99 bottles of beer on the wall", 1}, - { "apple_count", "%d apple(s)", "%d apple(s)", 1}, - { "apple_count", "%d apple(s)", "%d apple(s)", 42}, + {"Page", "Text for translation", "Text for translation", -1}, + {"Page", "Also text to translate", "Also text to translate", -1}, + {"installscript", "99 bottles of beer on the wall", "99 bottles of beer on the wall", -1}, + {"apple_count", "%d apple(s)", "%d apple(s)", 1}, + {"apple_count", "%d apple(s)", "%d apple(s)", 42}, }, - }, + }) +} - // QT Linguist, merging sections. Expecting .Duplicate_Key error because same key exists in more than 1 section. - { - file = "assets/I18N/duplicate-key.ts", +@(test) +test_qt_linguist_duplicate_key_err :: proc(t: ^testing.T) { + test(t, { // QT Linguist, merging sections. Expecting .Duplicate_Key error because same key exists in more than 1 section. + file = TEST_SUITE_PATH + "duplicate-key.ts", loader = i18n.parse_qt_linguist_file, + plural = nil, // Default pluralizer options = {merge_sections = true}, err = .Duplicate_Key, - }, + }) +} - // QT Linguist, not merging sections. Shouldn't return error despite same key existing in more than 1 section. - { - file = "assets/I18N/duplicate-key.ts", +@(test) +test_qt_linguist_duplicate_key :: proc(t: ^testing.T) { + test(t, { // QT Linguist, not merging sections. Shouldn't return error despite same key existing in more than 1 section. + file = TEST_SUITE_PATH + "duplicate-key.ts", loader = i18n.parse_qt_linguist_file, - }, + plural = nil, // Default pluralizer + }) } -@test -tests :: proc(t: ^testing.T) { - cat: ^i18n.Translation - err: i18n.Error +test :: proc(t: ^testing.T, suite: Test_Suite, loc := #caller_location) { + cat, err := suite.loader(suite.file, suite.options, suite.plural, context.allocator) + testing.expectf(t, err == suite.err, "Expected loading %v to return %v, got %v", suite.file, suite.err, err, loc=loc) - for suite in TESTS { - cat, err = suite.loader(suite.file, suite.options, nil, context.allocator) - - msg := fmt.tprintf("Expected loading %v to return %v, got %v", suite.file, suite.err, err) - expect(t, err == suite.err, msg) - - if err == .None { - for test in suite.tests { - val := T(test.section, test.key, test.n, cat) - - msg = fmt.tprintf("Expected key `%v` from section `%v`'s form for value `%v` to equal `%v`, got `%v`", test.key, test.section, test.n, test.val, val) - expect(t, val == test.val, msg) - } - } - i18n.destroy(cat) - } -} - -main :: proc() { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - t := testing.T{} - tests(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } - - if len(track.allocation_map) > 0 { - fmt.println() - for _, v in track.allocation_map { - fmt.printf("%v Leaked %v bytes.\n", v.location, v.size) + if err == .None { + for test in suite.tests { + val := test.n > -1 ? Tn(test.section, test.key, test.n, cat): T(test.section, test.key, cat) + testing.expectf(t, val == test.val, "Expected key `%v` from section `%v`'s form for value `%v` to equal `%v`, got `%v`", test.key, test.section, test.n, test.val, val, loc=loc) } } + i18n.destroy(cat) } \ No newline at end of file diff --git a/tests/core/text/match/test_core_text_match.odin b/tests/core/text/match/test_core_text_match.odin index b72190f78..5716b06fb 100644 --- a/tests/core/text/match/test_core_text_match.odin +++ b/tests/core/text/match/test_core_text_match.odin @@ -2,31 +2,6 @@ package test_strlib import "core:text/match" import "core:testing" -import "core:fmt" -import "core:os" -import "core:io" - -TEST_count: int -TEST_fail: int - -// inline expect with custom props -failed :: proc(t: ^testing.T, ok: bool, loc := #caller_location) -> bool { - TEST_count += 1 - - if !ok { - fmt.wprintf(t.w, "%v: ", loc) - t.error_count += 1 - TEST_fail += 1 - } - - return !ok -} - -expect :: testing.expect - -logf :: proc(t: ^testing.T, format: string, args: ..any) { - fmt.wprintf(t.w, format, ..args) -} // find correct byte offsets @test @@ -61,18 +36,17 @@ test_find :: proc(t: ^testing.T) { { "helelo", "h.-l", 0, { 0, 3, true } }, } - for entry, i in ENTRIES { + for entry in ENTRIES { matcher := match.matcher_init(entry.s, entry.p, entry.offset) start, end, ok := match.matcher_find(&matcher) success := entry.match.ok == ok && start == entry.match.start && end == entry.match.end - if failed(t, success) { - logf(t, "Find %d failed!\n", i) - logf(t, "\tHAYSTACK %s\tPATTERN %s\n", entry.s, entry.p) - logf(t, "\tSTART: %d == %d?\n", entry.match.start, start) - logf(t, "\tEND: %d == %d?\n", entry.match.end, end) - logf(t, "\tErr: %v\tLength %d\n", matcher.err, matcher.captures_length) - } + testing.expectf( + t, + success, + "HAYSTACK %q PATTERN %q, START: %d == %d? END: %d == %d? Err: %v Length %d", + entry.s, entry.p, entry.match.start, start, entry.match.end, end, matcher.err, matcher.captures_length, + ) } } @@ -178,17 +152,17 @@ test_match :: proc(t: ^testing.T) { { "testing _this_ out", "%b_", "", false }, } - for entry, i in ENTRIES { + for entry in ENTRIES { matcher := match.matcher_init(entry.s, entry.p) result, ok := match.matcher_match(&matcher) success := entry.ok == ok && result == entry.result - if failed(t, success) { - logf(t, "Match %d failed!\n", i) - logf(t, "\tHAYSTACK %s\tPATTERN %s\n", entry.s, entry.p) - logf(t, "\tResults: WANTED %s\tGOT %s\n", entry.result, result) - logf(t, "\tErr: %v\tLength %d\n", matcher.err, matcher.captures_length) - } + testing.expectf( + t, + success, + "HAYSTACK %q PATTERN %q WANTED %q GOT %q Err: %v Length %d", + entry.s, entry.p, entry.result, result, matcher.err, matcher.captures_length, + ) } } @@ -203,19 +177,23 @@ test_captures :: proc(t: ^testing.T) { compare_captures :: proc(t: ^testing.T, test: ^Temp, haystack: string, comp: []string, loc := #caller_location) { length, err := match.find_aux(haystack, test.pattern, 0, false, &test.captures) result := len(comp) == length && err == .OK - if failed(t, result == true) { - logf(t, "Captures Compare Failed!\n") - logf(t, "\tErr: %v\n", err) - logf(t, "\tLengths: %v != %v\n", len(comp), length) - } + testing.expectf( + t, + result, + "Captures Compare Failed! Lengths: %v != %v Err: %v", + len(comp), length, err, + ) for i in 0.. %s != %s\n", comp[i], text) - } + testing.expectf( + t, + comp[i] == text, + "Capture don't equal -> %q != %q\n", + comp[i], text, + ) } } @@ -224,11 +202,12 @@ test_captures :: proc(t: ^testing.T) { length, err := match.find_aux(haystack, test.pattern, 0, false, &test.captures) result := length > 0 && err == .OK - if failed(t, result == ok) { - logf(t, "Capture match failed!\n") - logf(t, "\tErr: %v\n", err) - logf(t, "\tLength: %v\n", length) - } + testing.expectf( + t, + result == ok, + "Capture match failed! Length: %v Pattern: %q Haystack: %q Err: %v", + length, test.pattern, haystack, err, + ) } temp := Temp { pattern = "(one).+" } @@ -253,15 +232,8 @@ test_captures :: proc(t: ^testing.T) { cap2 := captures[2] text1 := haystack[cap1.byte_start:cap1.byte_end] text2 := haystack[cap2.byte_start:cap2.byte_end] - expect(t, text1 == "233", "Multi-Capture failed at 1") - expect(t, text2 == "hello", "Multi-Capture failed at 2") - } -} - -gmatch_check :: proc(t: ^testing.T, index: int, a: []string, b: string) { - if failed(t, a[index] == b) { - logf(t, "GMATCH %d failed!\n", index) - logf(t, "\t%s != %s\n", a[index], b) + testing.expect(t, text1 == "233", "Multi-Capture failed at 1") + testing.expect(t, text2 == "hello", "Multi-Capture failed at 2") } } @@ -298,9 +270,9 @@ test_gmatch :: proc(t: ^testing.T) { @test test_gsub :: proc(t: ^testing.T) { result := match.gsub("testing123testing", "%d+", " sup ", context.temp_allocator) - expect(t, result == "testing sup testing", "GSUB 0: failed") + testing.expect(t, result == "testing sup testing", "GSUB 0: failed") result = match.gsub("testing123testing", "%a+", "345", context.temp_allocator) - expect(t, result == "345123345", "GSUB 1: failed") + testing.expect(t, result == "345123345", "GSUB 1: failed") } @test @@ -313,10 +285,12 @@ test_gfind :: proc(t: ^testing.T) { index: int for word in match.gfind(s, pattern, &captures) { - if failed(t, output[index] == word) { - logf(t, "GFIND %d failed!\n", index) - logf(t, "\t%s != %s\n", output[index], word) - } + testing.expectf( + t, + output[index] == word, + "GFIND %d failed! %q != %q", + index, output[index], word, + ) index += 1 } } @@ -332,11 +306,12 @@ test_frontier :: proc(t: ^testing.T) { call :: proc(data: rawptr, word: string, haystack: string, captures: []match.Match) { temp := cast(^Temp) data - if failed(temp.t, word == temp.output[temp.index]) { - logf(temp.t, "GSUB_WITH %d failed!\n", temp.index) - logf(temp.t, "\t%s != %s\n", temp.output[temp.index], word) - } - + testing.expectf( + temp.t, + word == temp.output[temp.index], + "GSUB_WITH %d failed! %q != %q", + temp.index, temp.output[temp.index], word, + ) temp.index += 1 } @@ -369,31 +344,21 @@ test_case_insensitive :: proc(t: ^testing.T) { pattern := match.pattern_case_insensitive("test", 256, context.temp_allocator) goal := "[tT][eE][sS][tT]" - if failed(t, pattern == goal) { - logf(t, "Case Insensitive Pattern doesn't match result\n") - logf(t, "\t%s != %s\n", pattern, goal) - } + testing.expectf( + t, + pattern == goal, + "Case Insensitive Pattern doesn't match result. %q != %q", + pattern, goal, + ) } } -main :: proc() { - t: testing.T - stream := os.stream_from_handle(os.stdout) - w := io.to_writer(stream) - t.w = w - - test_find(&t) - test_match(&t) - test_captures(&t) - test_gmatch(&t) - test_gsub(&t) - test_gfind(&t) - test_frontier(&t) - test_utf8(&t) - test_case_insensitive(&t) - - fmt.wprintf(w, "%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } +@(private) +gmatch_check :: proc(t: ^testing.T, index: int, a: []string, b: string) { + testing.expectf( + t, + a[index] == b, + "GMATCH %d failed! %q != %q", + index, a[index], b, + ) } \ No newline at end of file diff --git a/tests/core/thread/test_core_thread.odin b/tests/core/thread/test_core_thread.odin index c0c7396a7..0b77ad511 100644 --- a/tests/core/thread/test_core_thread.odin +++ b/tests/core/thread/test_core_thread.odin @@ -2,39 +2,7 @@ package test_core_thread import "core:testing" import "core:thread" -import "core:fmt" -import "core:os" - -TEST_count := 0 -TEST_fail := 0 - -t := &testing.T{} - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - poly_data_test(t) - - if TEST_fail > 0 { - os.exit(1) - } -} +import "base:intrinsics" @(test) poly_data_test :: proc(_t: ^testing.T) { @@ -46,7 +14,7 @@ poly_data_test :: proc(_t: ^testing.T) { b: [MAX]byte = 8 t1 := thread.create_and_start_with_poly_data(b, proc(b: [MAX]byte) { b_expect: [MAX]byte = 8 - expect(poly_data_test_t, b == b_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b == b_expect, "thread poly data not correct") }) defer free(t1) @@ -55,8 +23,8 @@ poly_data_test :: proc(_t: ^testing.T) { t2 := thread.create_and_start_with_poly_data2(b1, b2, proc(b: [3]uintptr, b2: [MAX / 2]byte) { b_expect: [3]uintptr = 1 b2_expect: [MAX / 2]byte = 3 - expect(poly_data_test_t, b == b_expect, "thread poly data not correct") - expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b == b_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct") }) defer free(t2) @@ -64,21 +32,21 @@ poly_data_test :: proc(_t: ^testing.T) { b_expect: [3]uintptr = 1 b2_expect: [MAX / 2]byte = 3 - expect(poly_data_test_t, b == b_expect, "thread poly data not correct") - expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct") - expect(poly_data_test_t, b3 == 333, "thread poly data not correct") + testing.expect(poly_data_test_t, b == b_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b3 == 333, "thread poly data not correct") }) defer free(t3) t4 := thread.create_and_start_with_poly_data4(uintptr(111), b1, uintptr(333), u8(5), proc(n: uintptr, b: [3]uintptr, n2: uintptr, n4: u8) { b_expect: [3]uintptr = 1 - expect(poly_data_test_t, n == 111, "thread poly data not correct") - expect(poly_data_test_t, b == b_expect, "thread poly data not correct") - expect(poly_data_test_t, n2 == 333, "thread poly data not correct") - expect(poly_data_test_t, n4 == 5, "thread poly data not correct") + testing.expect(poly_data_test_t, n == 111, "thread poly data not correct") + testing.expect(poly_data_test_t, b == b_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, n2 == 333, "thread poly data not correct") + testing.expect(poly_data_test_t, n4 == 5, "thread poly data not correct") }) defer free(t4) thread.join_multiple(t1, t2, t3, t4) -} +} \ No newline at end of file diff --git a/tests/core/time/test_core_time.odin b/tests/core/time/test_core_time.odin index 2cea47680..aeae44ca1 100644 --- a/tests/core/time/test_core_time.odin +++ b/tests/core/time/test_core_time.odin @@ -1,67 +1,17 @@ package test_core_time -import "core:fmt" -import "core:mem" -import "core:os" import "core:testing" import "core:time" import dt "core:time/datetime" is_leap_year :: time.is_leap_year -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - expect_value :: testing.expect_value - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - defer mem.tracking_allocator_destroy(&track) - context.allocator = mem.tracking_allocator(&track) - - test_ordinal_date_roundtrip(&t) - test_component_to_time_roundtrip(&t) - test_parse_rfc3339_string(&t) - - for _, leak in track.allocation_map { - expect(&t, false, fmt.tprintf("%v leaked %m\n", leak.location, leak.size)) - } - for bad_free in track.bad_free_array { - expect(&t, false, fmt.tprintf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)) - } - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - @test test_ordinal_date_roundtrip :: proc(t: ^testing.T) { - expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MIN_DATE)) == dt.MIN_DATE, "Roundtripping MIN_DATE failed.") - expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MIN_ORD)) == dt.MIN_ORD, "Roundtripping MIN_ORD failed.") - expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MAX_DATE)) == dt.MAX_DATE, "Roundtripping MAX_DATE failed.") - expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MAX_ORD)) == dt.MAX_ORD, "Roundtripping MAX_ORD failed.") + testing.expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MIN_DATE)) == dt.MIN_DATE, "Roundtripping MIN_DATE failed.") + testing.expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MIN_ORD)) == dt.MIN_ORD, "Roundtripping MIN_ORD failed.") + testing.expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MAX_DATE)) == dt.MAX_DATE, "Roundtripping MAX_DATE failed.") + testing.expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MAX_ORD)) == dt.MAX_ORD, "Roundtripping MAX_ORD failed.") } /* @@ -91,7 +41,46 @@ RFC3339_Test :: struct{ // These are based on RFC 3339's examples, see https://www.rfc-editor.org/rfc/rfc3339#page-10 rfc3339_tests :: []RFC3339_Test{ // This represents 20 minutes and 50.52 seconds after the 23rd hour of April 12th, 1985 in UTC. - {"1985-04-12T23:20:50.52Z", {482196050520000000}, true, 0, 23, false}, + {"1985-04-12 23:20:50.52Z", {482196050520000000}, true, 0, 23, false}, + // Same, but lowercase z + {"1985-04-12 23:20:50.52z", {482196050520000000}, true, 0, 23, false}, + + // This represents 39 minutes and 57 seconds after the 16th hour of December 19th, 1996 with an offset of -08:00 from UTC (Pacific Standard Time). + // Note that this is equivalent to 1996-12-20T00:39:57Z in UTC. + {"1996-12-19 16:39:57-08:00", {851013597000000000}, false, -480, 25, false}, + {"1996-12-19 16:39:57-08:00", {851042397000000000}, true, 0, 25, false}, + {"1996-12-20 00:39:57Z", {851042397000000000}, false, 0, 20, false}, + + // This represents the leap second inserted at the end of 1990. + // It'll be represented as 1990-12-31 23:59:59 UTC after parsing, and `is_leap` will be set to `true`. + {"1990-12-31 23:59:60Z", {662687999000000000}, true, 0, 20, true}, + + // This represents the same leap second in Pacific Standard Time, 8 hours behind UTC. + {"1990-12-31 15:59:60-08:00", {662687999000000000}, true, 0, 25, true}, + + // This represents the same instant of time as noon, January 1, 1937, Netherlands time. + // Standard time in the Netherlands was exactly 19 minutes and 32.13 seconds ahead of UTC by law + // from 1909-05-01 through 1937-06-30. This time zone cannot be represented exactly using the + // HH:MM format, and this timestamp uses the closest representable UTC offset. + {"1937-01-01 12:00:27.87+00:20", {-1041335972130000000}, false, 20, 28, false}, + {"1937-01-01 12:00:27.87+00:20", {-1041337172130000000}, true, 0, 28, false}, +} + +ISO8601_Test :: struct{ + iso_8601: string, + datetime: time.Time, + apply_offset: bool, + utc_offset: int, + consumed: int, + is_leap: bool, +} + +// These are based on RFC 3339's examples, see https://www.rfc-editor.org/rfc/rfc3339#page-10 +iso8601_tests :: []ISO8601_Test{ + // This represents 20 minutes and .003362 seconds after the 23rd hour of April 12th, 1985 in UTC. + {"1985-04-12T23:20:50.003362", {482196050003362000}, true, 0, 26, false}, + {"1985-04-12t23:20:50.003362", {482196050003362000}, true, 0, 26, false}, + {"1985-04-12 23:20:50.003362", {482196050003362000}, true, 0, 26, false}, // This represents 39 minutes and 57 seconds after the 16th hour of December 19th, 1996 with an offset of -08:00 from UTC (Pacific Standard Time). // Note that this is equivalent to 1996-12-20T00:39:57Z in UTC. @@ -110,8 +99,8 @@ rfc3339_tests :: []RFC3339_Test{ // Standard time in the Netherlands was exactly 19 minutes and 32.13 seconds ahead of UTC by law // from 1909-05-01 through 1937-06-30. This time zone cannot be represented exactly using the // HH:MM format, and this timestamp uses the closest representable UTC offset. - {"1937-01-01T12:00:27.87+00:20", {-1041335972130000000}, false, 20, 28, false}, - {"1937-01-01T12:00:27.87+00:20", {-1041337172130000000}, true, 0, 28, false}, + {"1937-01-01 12:00:27.87+00:20", {-1041335972130000000}, false, 20, 28, false}, + {"1937-01-01 12:00:27.87+00:20", {-1041337172130000000}, true, 0, 28, false}, } @test @@ -120,22 +109,108 @@ test_parse_rfc3339_string :: proc(t: ^testing.T) { is_leap := false if test.apply_offset { res, consumed := time.rfc3339_to_time_utc(test.rfc_3339, &is_leap) - msg := fmt.tprintf("[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", test.rfc_3339, res, res._nsec, test.consumed, consumed) - expect(t, test.consumed == consumed, msg) + testing.expectf( + t, + test.consumed == consumed, + "[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", + test.rfc_3339, res, res._nsec, test.consumed, consumed, + ) if test.consumed == consumed { - expect(t, test.datetime == res, fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec)) - expect(t, test.is_leap == is_leap, "Expected a leap second, got none.") + testing.expectf( + t, + test.datetime == res, + "Time didn't match. Expected %v (%v), got %v (%v)", + test.datetime, test.datetime._nsec, res, res._nsec, + ) + testing.expect( + t, + test.is_leap == is_leap, + "Expected a leap second, got none", + ) } } else { res, offset, consumed := time.rfc3339_to_time_and_offset(test.rfc_3339) - msg := fmt.tprintf("Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", test.rfc_3339, res, res._nsec, offset, test.consumed, consumed) - expect(t, test.consumed == consumed, msg) + testing.expectf( + t, + test.consumed == consumed, + "Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", + test.rfc_3339, res, res._nsec, offset, test.consumed, consumed, + ) if test.consumed == consumed { - expect(t, test.datetime == res, fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec)) - expect(t, test.utc_offset == offset, fmt.tprintf("UTC offset didn't match. Expected %v, got %v", test.utc_offset, offset)) - expect(t, test.is_leap == is_leap, "Expected a leap second, got none.") + testing.expectf( + t, test.datetime == res, + "Time didn't match. Expected %v (%v), got %v (%v)", + test.datetime, test.datetime._nsec, res, res._nsec, + ) + testing.expectf( + t, + test.utc_offset == offset, + "UTC offset didn't match. Expected %v, got %v", + test.utc_offset, offset, + ) + testing.expect( + t, test.is_leap == is_leap, + "Expected a leap second, got none", + ) + } + } + } +} + +@test +test_parse_iso8601_string :: proc(t: ^testing.T) { + for test in iso8601_tests { + is_leap := false + if test.apply_offset { + res, consumed := time.iso8601_to_time_utc(test.iso_8601, &is_leap) + testing.expectf( + t, + test.consumed == consumed, + "[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", + test.iso_8601, res, res._nsec, test.consumed, consumed, + ) + + if test.consumed == consumed { + testing.expectf( + t, + test.datetime == res, + "Time didn't match. Expected %v (%v), got %v (%v)", + test.datetime, test.datetime._nsec, res, res._nsec, + ) + testing.expect( + t, + test.is_leap == is_leap, + "Expected a leap second, got none", + ) + } + } else { + res, offset, consumed := time.iso8601_to_time_and_offset(test.iso_8601) + testing.expectf( + t, + test.consumed == consumed, + "Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", + test.iso_8601, res, res._nsec, offset, test.consumed, consumed, + ) + + if test.consumed == consumed { + testing.expectf( + t, test.datetime == res, + "Time didn't match. Expected %v (%v), got %v (%v)", + test.datetime, test.datetime._nsec, res, res._nsec, + ) + testing.expectf( + t, + test.utc_offset == offset, + "UTC offset didn't match. Expected %v, got %v", + test.utc_offset, offset, + ) + testing.expect( + t, + test.is_leap == is_leap, + "Expected a leap second, got none", + ) } } } @@ -164,15 +239,21 @@ test_component_to_time_roundtrip :: proc(t: ^testing.T) { date_component_roundtrip_test :: proc(t: ^testing.T, moment: dt.DateTime) { res, ok := time.datetime_to_time(moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second) - expect(t, ok, "Couldn't convert date components into date") + testing.expect( + t, + ok, + "Couldn't convert date components into date", + ) YYYY, MM, DD := time.date(res) hh, mm, ss := time.clock(res) - expected := fmt.tprintf("Expected %4d-%2d-%2d %2d:%2d:%2d, got %4d-%2d-%2d %2d:%2d:%2d", - moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, YYYY, MM, DD, hh, mm, ss) - ok = moment.year == i64(YYYY) && moment.month == i8(MM) && moment.day == i8(DD) ok &= moment.hour == i8(hh) && moment.minute == i8(mm) && moment.second == i8(ss) - expect(t, ok, expected) + testing.expectf( + t, + ok, + "Expected %4d-%2d-%2d %2d:%2d:%2d, got %4d-%2d-%2d %2d:%2d:%2d", + moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, YYYY, MM, DD, hh, mm, ss, + ) } \ No newline at end of file diff --git a/tests/core/unicode/test_core_unicode.odin b/tests/core/unicode/test_core_unicode.odin new file mode 100644 index 000000000..a1f6ac934 --- /dev/null +++ b/tests/core/unicode/test_core_unicode.odin @@ -0,0 +1,135 @@ +package test_core_unicode + +import "core:log" +import "core:testing" +import "core:unicode/utf8" + +Test_Case :: struct { + str: string, + expected_clusters: int, +} + +run_test_cases :: proc(t: ^testing.T, test_cases: []Test_Case, loc := #caller_location) { + failed := 0 + for c, i in test_cases { + log.debugf("(#% 4i) %q ...", i, c.str) + 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) + { + failed += 1 + } + } + + log.logf(.Error if failed > 0 else .Info, "% 4i/% 4i test cases failed.", failed, len(test_cases), location = loc) +} + +@test +test_official_gcb_cases :: proc(t: ^testing.T) { + run_test_cases(t, official_grapheme_break_test_cases) +} + +@test +test_official_emoji_cases :: proc(t: ^testing.T) { + run_test_cases(t, official_emoji_test_cases) +} + +@test +test_grapheme_byte_index_segmentation :: proc(t: ^testing.T) { + SAMPLE_1 :: "\U0001F600" + SAMPLE_2 :: "\U0001F3F4\U000E0067\U000E0062\U000E0065\U000E006E\U000E0067\U000E007F" + SAMPLE_3 :: "\U0001F468\U0001F3FB\u200D\U0001F9B0" + + str := SAMPLE_1 + SAMPLE_2 + SAMPLE_3 + SAMPLE_2 + SAMPLE_1 + + graphemes, _, _, _ := utf8.decode_grapheme_clusters(str) + defer delete(graphemes) + + defer if testing.failed(t) { + log.infof("%#v\n%q\n%v", graphemes, str, transmute([]u8)str) + } + if !testing.expect_value(t, len(graphemes), 5) { + return + } + + testing.expect_value(t, graphemes[0].rune_index, 0) + testing.expect_value(t, graphemes[1].rune_index, 1) + testing.expect_value(t, graphemes[2].rune_index, 8) + testing.expect_value(t, graphemes[3].rune_index, 12) + testing.expect_value(t, graphemes[4].rune_index, 19) + + grapheme_1 := str[graphemes[0].byte_index:graphemes[1].byte_index] + grapheme_2 := str[graphemes[1].byte_index:graphemes[2].byte_index] + grapheme_3 := str[graphemes[2].byte_index:graphemes[3].byte_index] + grapheme_4 := str[graphemes[3].byte_index:graphemes[4].byte_index] + grapheme_5 := str[graphemes[4].byte_index:] + + testing.expectf(t, grapheme_1 == SAMPLE_1, "expected %q, got %q", SAMPLE_1, grapheme_1) + testing.expectf(t, grapheme_2 == SAMPLE_2, "expected %q, got %q", SAMPLE_2, grapheme_2) + testing.expectf(t, grapheme_3 == SAMPLE_3, "expected %q, got %q", SAMPLE_3, grapheme_3) + testing.expectf(t, grapheme_4 == SAMPLE_2, "expected %q, got %q", SAMPLE_2, grapheme_2) + testing.expectf(t, grapheme_5 == SAMPLE_1, "expected %q, got %q", SAMPLE_1, grapheme_1) +} + +@test +test_width :: proc(t: ^testing.T) { + { + str := "He\u200dllo" + graphemes, _, width := utf8.grapheme_count(str) + testing.expect_value(t, graphemes, 5) + testing.expect_value(t, width, 5) + } + + { + // Note that a zero-width space is still considered a grapheme as far + // as the specification is concerned. + str := "He\u200bllo" + graphemes, _, width := utf8.grapheme_count(str) + testing.expect_value(t, graphemes, 6) + testing.expect_value(t, width, 5) + } + + { + str := "\U0001F926\U0001F3FC\u200D\u2642" + graphemes, _, width := utf8.grapheme_count(str) + testing.expect_value(t, graphemes, 1) + testing.expect_value(t, width, 2) + } + + { + str := "H̷e̶l̵l̸o̴p̵e̷ ̸w̶o̸r̵l̶d̵!̴" + graphemes, _, width := utf8.grapheme_count(str) + testing.expect_value(t, graphemes, 14) + testing.expect_value(t, width, 14) + } + + { + str := "aカ.ヒフ" + graphemes, grapheme_count, _, width := utf8.decode_grapheme_clusters(str) + defer delete(graphemes) + testing.expect_value(t, grapheme_count, 5) + testing.expect_value(t, width, 8) + if grapheme_count == 5 { + testing.expect_value(t, graphemes[0].width, 1) + testing.expect_value(t, graphemes[1].width, 2) + testing.expect_value(t, graphemes[2].width, 1) + testing.expect_value(t, graphemes[3].width, 2) + testing.expect_value(t, graphemes[4].width, 2) + } + } + + { + str := "いろはにほへ" + graphemes, _, width := utf8.grapheme_count(str) + testing.expect_value(t, graphemes, 6) + testing.expect_value(t, width, 12) + } + + { + str := "舍利弗,是諸法空相,不生不滅,不垢不淨,不增不減。" + graphemes, _, width := utf8.grapheme_count(str) + testing.expect_value(t, graphemes, 25) + testing.expect_value(t, width, 50) + } +} diff --git a/tests/core/unicode/test_core_unicode_data.odin b/tests/core/unicode/test_core_unicode_data.odin new file mode 100644 index 000000000..594af3c65 --- /dev/null +++ b/tests/core/unicode/test_core_unicode_data.odin @@ -0,0 +1,4912 @@ +package test_core_unicode + +// This file contains test data licensed under the Unicode Consortium that has +// been converted to a format that will work within Odin. +// +// The Unicode license to which said test data is under is included verbatim +// within this file, along with the names of the files from which the test data +// has been converted. + +/* +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE 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 OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. +*/ + + +// https://unicode.org/Public/15.1.0/ucd/auxiliary/GraphemeBreakTest.txt +// +// GraphemeBreakTest-15.1.0.txt +// Date: 2023-08-07, 15:52:55 GMT +// © 2023 Unicode®, Inc. +// Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +// For terms of use, see https://www.unicode.org/terms_of_use.html +@(rodata) +official_grapheme_break_test_cases := []Test_Case { +{"\u0020\u0020", 2}, +{"\u0020\u0308\u0020", 2}, +{"\u0020\u000D", 2}, +{"\u0020\u0308\u000D", 2}, +{"\u0020\u000A", 2}, +{"\u0020\u0308\u000A", 2}, +{"\u0020\u0001", 2}, +{"\u0020\u0308\u0001", 2}, +{"\u0020\u034F", 1}, +{"\u0020\u0308\u034F", 1}, +{"\u0020\U0001F1E6", 2}, +{"\u0020\u0308\U0001F1E6", 2}, +{"\u0020\u0600", 2}, +{"\u0020\u0308\u0600", 2}, +{"\u0020\u0A03", 1}, +{"\u0020\u0308\u0A03", 1}, +{"\u0020\u1100", 2}, +{"\u0020\u0308\u1100", 2}, +{"\u0020\u1160", 2}, +{"\u0020\u0308\u1160", 2}, +{"\u0020\u11A8", 2}, +{"\u0020\u0308\u11A8", 2}, +{"\u0020\uAC00", 2}, +{"\u0020\u0308\uAC00", 2}, +{"\u0020\uAC01", 2}, +{"\u0020\u0308\uAC01", 2}, +{"\u0020\u0900", 1}, +{"\u0020\u0308\u0900", 1}, +{"\u0020\u0903", 1}, +{"\u0020\u0308\u0903", 1}, +{"\u0020\u0904", 2}, +{"\u0020\u0308\u0904", 2}, +{"\u0020\u0D4E", 2}, +{"\u0020\u0308\u0D4E", 2}, +{"\u0020\u0915", 2}, +{"\u0020\u0308\u0915", 2}, +{"\u0020\u231A", 2}, +{"\u0020\u0308\u231A", 2}, +{"\u0020\u0300", 1}, +{"\u0020\u0308\u0300", 1}, +{"\u0020\u093C", 1}, +{"\u0020\u0308\u093C", 1}, +{"\u0020\u094D", 1}, +{"\u0020\u0308\u094D", 1}, +{"\u0020\u200D", 1}, +{"\u0020\u0308\u200D", 1}, +{"\u0020\u0378", 2}, +{"\u0020\u0308\u0378", 2}, +{"\u000D\u0020", 2}, +{"\u000D\u0308\u0020", 3}, +{"\u000D\u000D", 2}, +{"\u000D\u0308\u000D", 3}, +{"\u000D\u000A", 1}, +{"\u000D\u0308\u000A", 3}, +{"\u000D\u0001", 2}, +{"\u000D\u0308\u0001", 3}, +{"\u000D\u034F", 2}, +{"\u000D\u0308\u034F", 2}, +{"\u000D\U0001F1E6", 2}, +{"\u000D\u0308\U0001F1E6", 3}, +{"\u000D\u0600", 2}, +{"\u000D\u0308\u0600", 3}, +{"\u000D\u0A03", 2}, +{"\u000D\u0308\u0A03", 2}, +{"\u000D\u1100", 2}, +{"\u000D\u0308\u1100", 3}, +{"\u000D\u1160", 2}, +{"\u000D\u0308\u1160", 3}, +{"\u000D\u11A8", 2}, +{"\u000D\u0308\u11A8", 3}, +{"\u000D\uAC00", 2}, +{"\u000D\u0308\uAC00", 3}, +{"\u000D\uAC01", 2}, +{"\u000D\u0308\uAC01", 3}, +{"\u000D\u0900", 2}, +{"\u000D\u0308\u0900", 2}, +{"\u000D\u0903", 2}, +{"\u000D\u0308\u0903", 2}, +{"\u000D\u0904", 2}, +{"\u000D\u0308\u0904", 3}, +{"\u000D\u0D4E", 2}, +{"\u000D\u0308\u0D4E", 3}, +{"\u000D\u0915", 2}, +{"\u000D\u0308\u0915", 3}, +{"\u000D\u231A", 2}, +{"\u000D\u0308\u231A", 3}, +{"\u000D\u0300", 2}, +{"\u000D\u0308\u0300", 2}, +{"\u000D\u093C", 2}, +{"\u000D\u0308\u093C", 2}, +{"\u000D\u094D", 2}, +{"\u000D\u0308\u094D", 2}, +{"\u000D\u200D", 2}, +{"\u000D\u0308\u200D", 2}, +{"\u000D\u0378", 2}, +{"\u000D\u0308\u0378", 3}, +{"\u000A\u0020", 2}, +{"\u000A\u0308\u0020", 3}, +{"\u000A\u000D", 2}, +{"\u000A\u0308\u000D", 3}, +{"\u000A\u000A", 2}, +{"\u000A\u0308\u000A", 3}, +{"\u000A\u0001", 2}, +{"\u000A\u0308\u0001", 3}, +{"\u000A\u034F", 2}, +{"\u000A\u0308\u034F", 2}, +{"\u000A\U0001F1E6", 2}, +{"\u000A\u0308\U0001F1E6", 3}, +{"\u000A\u0600", 2}, +{"\u000A\u0308\u0600", 3}, +{"\u000A\u0A03", 2}, +{"\u000A\u0308\u0A03", 2}, +{"\u000A\u1100", 2}, +{"\u000A\u0308\u1100", 3}, +{"\u000A\u1160", 2}, +{"\u000A\u0308\u1160", 3}, +{"\u000A\u11A8", 2}, +{"\u000A\u0308\u11A8", 3}, +{"\u000A\uAC00", 2}, +{"\u000A\u0308\uAC00", 3}, +{"\u000A\uAC01", 2}, +{"\u000A\u0308\uAC01", 3}, +{"\u000A\u0900", 2}, +{"\u000A\u0308\u0900", 2}, +{"\u000A\u0903", 2}, +{"\u000A\u0308\u0903", 2}, +{"\u000A\u0904", 2}, +{"\u000A\u0308\u0904", 3}, +{"\u000A\u0D4E", 2}, +{"\u000A\u0308\u0D4E", 3}, +{"\u000A\u0915", 2}, +{"\u000A\u0308\u0915", 3}, +{"\u000A\u231A", 2}, +{"\u000A\u0308\u231A", 3}, +{"\u000A\u0300", 2}, +{"\u000A\u0308\u0300", 2}, +{"\u000A\u093C", 2}, +{"\u000A\u0308\u093C", 2}, +{"\u000A\u094D", 2}, +{"\u000A\u0308\u094D", 2}, +{"\u000A\u200D", 2}, +{"\u000A\u0308\u200D", 2}, +{"\u000A\u0378", 2}, +{"\u000A\u0308\u0378", 3}, +{"\u0001\u0020", 2}, +{"\u0001\u0308\u0020", 3}, +{"\u0001\u000D", 2}, +{"\u0001\u0308\u000D", 3}, +{"\u0001\u000A", 2}, +{"\u0001\u0308\u000A", 3}, +{"\u0001\u0001", 2}, +{"\u0001\u0308\u0001", 3}, +{"\u0001\u034F", 2}, +{"\u0001\u0308\u034F", 2}, +{"\u0001\U0001F1E6", 2}, +{"\u0001\u0308\U0001F1E6", 3}, +{"\u0001\u0600", 2}, +{"\u0001\u0308\u0600", 3}, +{"\u0001\u0A03", 2}, +{"\u0001\u0308\u0A03", 2}, +{"\u0001\u1100", 2}, +{"\u0001\u0308\u1100", 3}, +{"\u0001\u1160", 2}, +{"\u0001\u0308\u1160", 3}, +{"\u0001\u11A8", 2}, +{"\u0001\u0308\u11A8", 3}, +{"\u0001\uAC00", 2}, +{"\u0001\u0308\uAC00", 3}, +{"\u0001\uAC01", 2}, +{"\u0001\u0308\uAC01", 3}, +{"\u0001\u0900", 2}, +{"\u0001\u0308\u0900", 2}, +{"\u0001\u0903", 2}, +{"\u0001\u0308\u0903", 2}, +{"\u0001\u0904", 2}, +{"\u0001\u0308\u0904", 3}, +{"\u0001\u0D4E", 2}, +{"\u0001\u0308\u0D4E", 3}, +{"\u0001\u0915", 2}, +{"\u0001\u0308\u0915", 3}, +{"\u0001\u231A", 2}, +{"\u0001\u0308\u231A", 3}, +{"\u0001\u0300", 2}, +{"\u0001\u0308\u0300", 2}, +{"\u0001\u093C", 2}, +{"\u0001\u0308\u093C", 2}, +{"\u0001\u094D", 2}, +{"\u0001\u0308\u094D", 2}, +{"\u0001\u200D", 2}, +{"\u0001\u0308\u200D", 2}, +{"\u0001\u0378", 2}, +{"\u0001\u0308\u0378", 3}, +{"\u034F\u0020", 2}, +{"\u034F\u0308\u0020", 2}, +{"\u034F\u000D", 2}, +{"\u034F\u0308\u000D", 2}, +{"\u034F\u000A", 2}, +{"\u034F\u0308\u000A", 2}, +{"\u034F\u0001", 2}, +{"\u034F\u0308\u0001", 2}, +{"\u034F\u034F", 1}, +{"\u034F\u0308\u034F", 1}, +{"\u034F\U0001F1E6", 2}, +{"\u034F\u0308\U0001F1E6", 2}, +{"\u034F\u0600", 2}, +{"\u034F\u0308\u0600", 2}, +{"\u034F\u0A03", 1}, +{"\u034F\u0308\u0A03", 1}, +{"\u034F\u1100", 2}, +{"\u034F\u0308\u1100", 2}, +{"\u034F\u1160", 2}, +{"\u034F\u0308\u1160", 2}, +{"\u034F\u11A8", 2}, +{"\u034F\u0308\u11A8", 2}, +{"\u034F\uAC00", 2}, +{"\u034F\u0308\uAC00", 2}, +{"\u034F\uAC01", 2}, +{"\u034F\u0308\uAC01", 2}, +{"\u034F\u0900", 1}, +{"\u034F\u0308\u0900", 1}, +{"\u034F\u0903", 1}, +{"\u034F\u0308\u0903", 1}, +{"\u034F\u0904", 2}, +{"\u034F\u0308\u0904", 2}, +{"\u034F\u0D4E", 2}, +{"\u034F\u0308\u0D4E", 2}, +{"\u034F\u0915", 2}, +{"\u034F\u0308\u0915", 2}, +{"\u034F\u231A", 2}, +{"\u034F\u0308\u231A", 2}, +{"\u034F\u0300", 1}, +{"\u034F\u0308\u0300", 1}, +{"\u034F\u093C", 1}, +{"\u034F\u0308\u093C", 1}, +{"\u034F\u094D", 1}, +{"\u034F\u0308\u094D", 1}, +{"\u034F\u200D", 1}, +{"\u034F\u0308\u200D", 1}, +{"\u034F\u0378", 2}, +{"\u034F\u0308\u0378", 2}, +{"\U0001F1E6\u0020", 2}, +{"\U0001F1E6\u0308\u0020", 2}, +{"\U0001F1E6\u000D", 2}, +{"\U0001F1E6\u0308\u000D", 2}, +{"\U0001F1E6\u000A", 2}, +{"\U0001F1E6\u0308\u000A", 2}, +{"\U0001F1E6\u0001", 2}, +{"\U0001F1E6\u0308\u0001", 2}, +{"\U0001F1E6\u034F", 1}, +{"\U0001F1E6\u0308\u034F", 1}, +{"\U0001F1E6\U0001F1E6", 1}, +{"\U0001F1E6\u0308\U0001F1E6", 2}, +{"\U0001F1E6\u0600", 2}, +{"\U0001F1E6\u0308\u0600", 2}, +{"\U0001F1E6\u0A03", 1}, +{"\U0001F1E6\u0308\u0A03", 1}, +{"\U0001F1E6\u1100", 2}, +{"\U0001F1E6\u0308\u1100", 2}, +{"\U0001F1E6\u1160", 2}, +{"\U0001F1E6\u0308\u1160", 2}, +{"\U0001F1E6\u11A8", 2}, +{"\U0001F1E6\u0308\u11A8", 2}, +{"\U0001F1E6\uAC00", 2}, +{"\U0001F1E6\u0308\uAC00", 2}, +{"\U0001F1E6\uAC01", 2}, +{"\U0001F1E6\u0308\uAC01", 2}, +{"\U0001F1E6\u0900", 1}, +{"\U0001F1E6\u0308\u0900", 1}, +{"\U0001F1E6\u0903", 1}, +{"\U0001F1E6\u0308\u0903", 1}, +{"\U0001F1E6\u0904", 2}, +{"\U0001F1E6\u0308\u0904", 2}, +{"\U0001F1E6\u0D4E", 2}, +{"\U0001F1E6\u0308\u0D4E", 2}, +{"\U0001F1E6\u0915", 2}, +{"\U0001F1E6\u0308\u0915", 2}, +{"\U0001F1E6\u231A", 2}, +{"\U0001F1E6\u0308\u231A", 2}, +{"\U0001F1E6\u0300", 1}, +{"\U0001F1E6\u0308\u0300", 1}, +{"\U0001F1E6\u093C", 1}, +{"\U0001F1E6\u0308\u093C", 1}, +{"\U0001F1E6\u094D", 1}, +{"\U0001F1E6\u0308\u094D", 1}, +{"\U0001F1E6\u200D", 1}, +{"\U0001F1E6\u0308\u200D", 1}, +{"\U0001F1E6\u0378", 2}, +{"\U0001F1E6\u0308\u0378", 2}, +{"\u0600\u0020", 1}, +{"\u0600\u0308\u0020", 2}, +{"\u0600\u000D", 2}, +{"\u0600\u0308\u000D", 2}, +{"\u0600\u000A", 2}, +{"\u0600\u0308\u000A", 2}, +{"\u0600\u0001", 2}, +{"\u0600\u0308\u0001", 2}, +{"\u0600\u034F", 1}, +{"\u0600\u0308\u034F", 1}, +{"\u0600\U0001F1E6", 1}, +{"\u0600\u0308\U0001F1E6", 2}, +{"\u0600\u0600", 1}, +{"\u0600\u0308\u0600", 2}, +{"\u0600\u0A03", 1}, +{"\u0600\u0308\u0A03", 1}, +{"\u0600\u1100", 1}, +{"\u0600\u0308\u1100", 2}, +{"\u0600\u1160", 1}, +{"\u0600\u0308\u1160", 2}, +{"\u0600\u11A8", 1}, +{"\u0600\u0308\u11A8", 2}, +{"\u0600\uAC00", 1}, +{"\u0600\u0308\uAC00", 2}, +{"\u0600\uAC01", 1}, +{"\u0600\u0308\uAC01", 2}, +{"\u0600\u0900", 1}, +{"\u0600\u0308\u0900", 1}, +{"\u0600\u0903", 1}, +{"\u0600\u0308\u0903", 1}, +{"\u0600\u0904", 1}, +{"\u0600\u0308\u0904", 2}, +{"\u0600\u0D4E", 1}, +{"\u0600\u0308\u0D4E", 2}, +{"\u0600\u0915", 1}, +{"\u0600\u0308\u0915", 2}, +{"\u0600\u231A", 1}, +{"\u0600\u0308\u231A", 2}, +{"\u0600\u0300", 1}, +{"\u0600\u0308\u0300", 1}, +{"\u0600\u093C", 1}, +{"\u0600\u0308\u093C", 1}, +{"\u0600\u094D", 1}, +{"\u0600\u0308\u094D", 1}, +{"\u0600\u200D", 1}, +{"\u0600\u0308\u200D", 1}, +{"\u0600\u0378", 1}, +{"\u0600\u0308\u0378", 2}, +{"\u0A03\u0020", 2}, +{"\u0A03\u0308\u0020", 2}, +{"\u0A03\u000D", 2}, +{"\u0A03\u0308\u000D", 2}, +{"\u0A03\u000A", 2}, +{"\u0A03\u0308\u000A", 2}, +{"\u0A03\u0001", 2}, +{"\u0A03\u0308\u0001", 2}, +{"\u0A03\u034F", 1}, +{"\u0A03\u0308\u034F", 1}, +{"\u0A03\U0001F1E6", 2}, +{"\u0A03\u0308\U0001F1E6", 2}, +{"\u0A03\u0600", 2}, +{"\u0A03\u0308\u0600", 2}, +{"\u0A03\u0A03", 1}, +{"\u0A03\u0308\u0A03", 1}, +{"\u0A03\u1100", 2}, +{"\u0A03\u0308\u1100", 2}, +{"\u0A03\u1160", 2}, +{"\u0A03\u0308\u1160", 2}, +{"\u0A03\u11A8", 2}, +{"\u0A03\u0308\u11A8", 2}, +{"\u0A03\uAC00", 2}, +{"\u0A03\u0308\uAC00", 2}, +{"\u0A03\uAC01", 2}, +{"\u0A03\u0308\uAC01", 2}, +{"\u0A03\u0900", 1}, +{"\u0A03\u0308\u0900", 1}, +{"\u0A03\u0903", 1}, +{"\u0A03\u0308\u0903", 1}, +{"\u0A03\u0904", 2}, +{"\u0A03\u0308\u0904", 2}, +{"\u0A03\u0D4E", 2}, +{"\u0A03\u0308\u0D4E", 2}, +{"\u0A03\u0915", 2}, +{"\u0A03\u0308\u0915", 2}, +{"\u0A03\u231A", 2}, +{"\u0A03\u0308\u231A", 2}, +{"\u0A03\u0300", 1}, +{"\u0A03\u0308\u0300", 1}, +{"\u0A03\u093C", 1}, +{"\u0A03\u0308\u093C", 1}, +{"\u0A03\u094D", 1}, +{"\u0A03\u0308\u094D", 1}, +{"\u0A03\u200D", 1}, +{"\u0A03\u0308\u200D", 1}, +{"\u0A03\u0378", 2}, +{"\u0A03\u0308\u0378", 2}, +{"\u1100\u0020", 2}, +{"\u1100\u0308\u0020", 2}, +{"\u1100\u000D", 2}, +{"\u1100\u0308\u000D", 2}, +{"\u1100\u000A", 2}, +{"\u1100\u0308\u000A", 2}, +{"\u1100\u0001", 2}, +{"\u1100\u0308\u0001", 2}, +{"\u1100\u034F", 1}, +{"\u1100\u0308\u034F", 1}, +{"\u1100\U0001F1E6", 2}, +{"\u1100\u0308\U0001F1E6", 2}, +{"\u1100\u0600", 2}, +{"\u1100\u0308\u0600", 2}, +{"\u1100\u0A03", 1}, +{"\u1100\u0308\u0A03", 1}, +{"\u1100\u1100", 1}, +{"\u1100\u0308\u1100", 2}, +{"\u1100\u1160", 1}, +{"\u1100\u0308\u1160", 2}, +{"\u1100\u11A8", 2}, +{"\u1100\u0308\u11A8", 2}, +{"\u1100\uAC00", 1}, +{"\u1100\u0308\uAC00", 2}, +{"\u1100\uAC01", 1}, +{"\u1100\u0308\uAC01", 2}, +{"\u1100\u0900", 1}, +{"\u1100\u0308\u0900", 1}, +{"\u1100\u0903", 1}, +{"\u1100\u0308\u0903", 1}, +{"\u1100\u0904", 2}, +{"\u1100\u0308\u0904", 2}, +{"\u1100\u0D4E", 2}, +{"\u1100\u0308\u0D4E", 2}, +{"\u1100\u0915", 2}, +{"\u1100\u0308\u0915", 2}, +{"\u1100\u231A", 2}, +{"\u1100\u0308\u231A", 2}, +{"\u1100\u0300", 1}, +{"\u1100\u0308\u0300", 1}, +{"\u1100\u093C", 1}, +{"\u1100\u0308\u093C", 1}, +{"\u1100\u094D", 1}, +{"\u1100\u0308\u094D", 1}, +{"\u1100\u200D", 1}, +{"\u1100\u0308\u200D", 1}, +{"\u1100\u0378", 2}, +{"\u1100\u0308\u0378", 2}, +{"\u1160\u0020", 2}, +{"\u1160\u0308\u0020", 2}, +{"\u1160\u000D", 2}, +{"\u1160\u0308\u000D", 2}, +{"\u1160\u000A", 2}, +{"\u1160\u0308\u000A", 2}, +{"\u1160\u0001", 2}, +{"\u1160\u0308\u0001", 2}, +{"\u1160\u034F", 1}, +{"\u1160\u0308\u034F", 1}, +{"\u1160\U0001F1E6", 2}, +{"\u1160\u0308\U0001F1E6", 2}, +{"\u1160\u0600", 2}, +{"\u1160\u0308\u0600", 2}, +{"\u1160\u0A03", 1}, +{"\u1160\u0308\u0A03", 1}, +{"\u1160\u1100", 2}, +{"\u1160\u0308\u1100", 2}, +{"\u1160\u1160", 1}, +{"\u1160\u0308\u1160", 2}, +{"\u1160\u11A8", 1}, +{"\u1160\u0308\u11A8", 2}, +{"\u1160\uAC00", 2}, +{"\u1160\u0308\uAC00", 2}, +{"\u1160\uAC01", 2}, +{"\u1160\u0308\uAC01", 2}, +{"\u1160\u0900", 1}, +{"\u1160\u0308\u0900", 1}, +{"\u1160\u0903", 1}, +{"\u1160\u0308\u0903", 1}, +{"\u1160\u0904", 2}, +{"\u1160\u0308\u0904", 2}, +{"\u1160\u0D4E", 2}, +{"\u1160\u0308\u0D4E", 2}, +{"\u1160\u0915", 2}, +{"\u1160\u0308\u0915", 2}, +{"\u1160\u231A", 2}, +{"\u1160\u0308\u231A", 2}, +{"\u1160\u0300", 1}, +{"\u1160\u0308\u0300", 1}, +{"\u1160\u093C", 1}, +{"\u1160\u0308\u093C", 1}, +{"\u1160\u094D", 1}, +{"\u1160\u0308\u094D", 1}, +{"\u1160\u200D", 1}, +{"\u1160\u0308\u200D", 1}, +{"\u1160\u0378", 2}, +{"\u1160\u0308\u0378", 2}, +{"\u11A8\u0020", 2}, +{"\u11A8\u0308\u0020", 2}, +{"\u11A8\u000D", 2}, +{"\u11A8\u0308\u000D", 2}, +{"\u11A8\u000A", 2}, +{"\u11A8\u0308\u000A", 2}, +{"\u11A8\u0001", 2}, +{"\u11A8\u0308\u0001", 2}, +{"\u11A8\u034F", 1}, +{"\u11A8\u0308\u034F", 1}, +{"\u11A8\U0001F1E6", 2}, +{"\u11A8\u0308\U0001F1E6", 2}, +{"\u11A8\u0600", 2}, +{"\u11A8\u0308\u0600", 2}, +{"\u11A8\u0A03", 1}, +{"\u11A8\u0308\u0A03", 1}, +{"\u11A8\u1100", 2}, +{"\u11A8\u0308\u1100", 2}, +{"\u11A8\u1160", 2}, +{"\u11A8\u0308\u1160", 2}, +{"\u11A8\u11A8", 1}, +{"\u11A8\u0308\u11A8", 2}, +{"\u11A8\uAC00", 2}, +{"\u11A8\u0308\uAC00", 2}, +{"\u11A8\uAC01", 2}, +{"\u11A8\u0308\uAC01", 2}, +{"\u11A8\u0900", 1}, +{"\u11A8\u0308\u0900", 1}, +{"\u11A8\u0903", 1}, +{"\u11A8\u0308\u0903", 1}, +{"\u11A8\u0904", 2}, +{"\u11A8\u0308\u0904", 2}, +{"\u11A8\u0D4E", 2}, +{"\u11A8\u0308\u0D4E", 2}, +{"\u11A8\u0915", 2}, +{"\u11A8\u0308\u0915", 2}, +{"\u11A8\u231A", 2}, +{"\u11A8\u0308\u231A", 2}, +{"\u11A8\u0300", 1}, +{"\u11A8\u0308\u0300", 1}, +{"\u11A8\u093C", 1}, +{"\u11A8\u0308\u093C", 1}, +{"\u11A8\u094D", 1}, +{"\u11A8\u0308\u094D", 1}, +{"\u11A8\u200D", 1}, +{"\u11A8\u0308\u200D", 1}, +{"\u11A8\u0378", 2}, +{"\u11A8\u0308\u0378", 2}, +{"\uAC00\u0020", 2}, +{"\uAC00\u0308\u0020", 2}, +{"\uAC00\u000D", 2}, +{"\uAC00\u0308\u000D", 2}, +{"\uAC00\u000A", 2}, +{"\uAC00\u0308\u000A", 2}, +{"\uAC00\u0001", 2}, +{"\uAC00\u0308\u0001", 2}, +{"\uAC00\u034F", 1}, +{"\uAC00\u0308\u034F", 1}, +{"\uAC00\U0001F1E6", 2}, +{"\uAC00\u0308\U0001F1E6", 2}, +{"\uAC00\u0600", 2}, +{"\uAC00\u0308\u0600", 2}, +{"\uAC00\u0A03", 1}, +{"\uAC00\u0308\u0A03", 1}, +{"\uAC00\u1100", 2}, +{"\uAC00\u0308\u1100", 2}, +{"\uAC00\u1160", 1}, +{"\uAC00\u0308\u1160", 2}, +{"\uAC00\u11A8", 1}, +{"\uAC00\u0308\u11A8", 2}, +{"\uAC00\uAC00", 2}, +{"\uAC00\u0308\uAC00", 2}, +{"\uAC00\uAC01", 2}, +{"\uAC00\u0308\uAC01", 2}, +{"\uAC00\u0900", 1}, +{"\uAC00\u0308\u0900", 1}, +{"\uAC00\u0903", 1}, +{"\uAC00\u0308\u0903", 1}, +{"\uAC00\u0904", 2}, +{"\uAC00\u0308\u0904", 2}, +{"\uAC00\u0D4E", 2}, +{"\uAC00\u0308\u0D4E", 2}, +{"\uAC00\u0915", 2}, +{"\uAC00\u0308\u0915", 2}, +{"\uAC00\u231A", 2}, +{"\uAC00\u0308\u231A", 2}, +{"\uAC00\u0300", 1}, +{"\uAC00\u0308\u0300", 1}, +{"\uAC00\u093C", 1}, +{"\uAC00\u0308\u093C", 1}, +{"\uAC00\u094D", 1}, +{"\uAC00\u0308\u094D", 1}, +{"\uAC00\u200D", 1}, +{"\uAC00\u0308\u200D", 1}, +{"\uAC00\u0378", 2}, +{"\uAC00\u0308\u0378", 2}, +{"\uAC01\u0020", 2}, +{"\uAC01\u0308\u0020", 2}, +{"\uAC01\u000D", 2}, +{"\uAC01\u0308\u000D", 2}, +{"\uAC01\u000A", 2}, +{"\uAC01\u0308\u000A", 2}, +{"\uAC01\u0001", 2}, +{"\uAC01\u0308\u0001", 2}, +{"\uAC01\u034F", 1}, +{"\uAC01\u0308\u034F", 1}, +{"\uAC01\U0001F1E6", 2}, +{"\uAC01\u0308\U0001F1E6", 2}, +{"\uAC01\u0600", 2}, +{"\uAC01\u0308\u0600", 2}, +{"\uAC01\u0A03", 1}, +{"\uAC01\u0308\u0A03", 1}, +{"\uAC01\u1100", 2}, +{"\uAC01\u0308\u1100", 2}, +{"\uAC01\u1160", 2}, +{"\uAC01\u0308\u1160", 2}, +{"\uAC01\u11A8", 1}, +{"\uAC01\u0308\u11A8", 2}, +{"\uAC01\uAC00", 2}, +{"\uAC01\u0308\uAC00", 2}, +{"\uAC01\uAC01", 2}, +{"\uAC01\u0308\uAC01", 2}, +{"\uAC01\u0900", 1}, +{"\uAC01\u0308\u0900", 1}, +{"\uAC01\u0903", 1}, +{"\uAC01\u0308\u0903", 1}, +{"\uAC01\u0904", 2}, +{"\uAC01\u0308\u0904", 2}, +{"\uAC01\u0D4E", 2}, +{"\uAC01\u0308\u0D4E", 2}, +{"\uAC01\u0915", 2}, +{"\uAC01\u0308\u0915", 2}, +{"\uAC01\u231A", 2}, +{"\uAC01\u0308\u231A", 2}, +{"\uAC01\u0300", 1}, +{"\uAC01\u0308\u0300", 1}, +{"\uAC01\u093C", 1}, +{"\uAC01\u0308\u093C", 1}, +{"\uAC01\u094D", 1}, +{"\uAC01\u0308\u094D", 1}, +{"\uAC01\u200D", 1}, +{"\uAC01\u0308\u200D", 1}, +{"\uAC01\u0378", 2}, +{"\uAC01\u0308\u0378", 2}, +{"\u0900\u0020", 2}, +{"\u0900\u0308\u0020", 2}, +{"\u0900\u000D", 2}, +{"\u0900\u0308\u000D", 2}, +{"\u0900\u000A", 2}, +{"\u0900\u0308\u000A", 2}, +{"\u0900\u0001", 2}, +{"\u0900\u0308\u0001", 2}, +{"\u0900\u034F", 1}, +{"\u0900\u0308\u034F", 1}, +{"\u0900\U0001F1E6", 2}, +{"\u0900\u0308\U0001F1E6", 2}, +{"\u0900\u0600", 2}, +{"\u0900\u0308\u0600", 2}, +{"\u0900\u0A03", 1}, +{"\u0900\u0308\u0A03", 1}, +{"\u0900\u1100", 2}, +{"\u0900\u0308\u1100", 2}, +{"\u0900\u1160", 2}, +{"\u0900\u0308\u1160", 2}, +{"\u0900\u11A8", 2}, +{"\u0900\u0308\u11A8", 2}, +{"\u0900\uAC00", 2}, +{"\u0900\u0308\uAC00", 2}, +{"\u0900\uAC01", 2}, +{"\u0900\u0308\uAC01", 2}, +{"\u0900\u0900", 1}, +{"\u0900\u0308\u0900", 1}, +{"\u0900\u0903", 1}, +{"\u0900\u0308\u0903", 1}, +{"\u0900\u0904", 2}, +{"\u0900\u0308\u0904", 2}, +{"\u0900\u0D4E", 2}, +{"\u0900\u0308\u0D4E", 2}, +{"\u0900\u0915", 2}, +{"\u0900\u0308\u0915", 2}, +{"\u0900\u231A", 2}, +{"\u0900\u0308\u231A", 2}, +{"\u0900\u0300", 1}, +{"\u0900\u0308\u0300", 1}, +{"\u0900\u093C", 1}, +{"\u0900\u0308\u093C", 1}, +{"\u0900\u094D", 1}, +{"\u0900\u0308\u094D", 1}, +{"\u0900\u200D", 1}, +{"\u0900\u0308\u200D", 1}, +{"\u0900\u0378", 2}, +{"\u0900\u0308\u0378", 2}, +{"\u0903\u0020", 2}, +{"\u0903\u0308\u0020", 2}, +{"\u0903\u000D", 2}, +{"\u0903\u0308\u000D", 2}, +{"\u0903\u000A", 2}, +{"\u0903\u0308\u000A", 2}, +{"\u0903\u0001", 2}, +{"\u0903\u0308\u0001", 2}, +{"\u0903\u034F", 1}, +{"\u0903\u0308\u034F", 1}, +{"\u0903\U0001F1E6", 2}, +{"\u0903\u0308\U0001F1E6", 2}, +{"\u0903\u0600", 2}, +{"\u0903\u0308\u0600", 2}, +{"\u0903\u0A03", 1}, +{"\u0903\u0308\u0A03", 1}, +{"\u0903\u1100", 2}, +{"\u0903\u0308\u1100", 2}, +{"\u0903\u1160", 2}, +{"\u0903\u0308\u1160", 2}, +{"\u0903\u11A8", 2}, +{"\u0903\u0308\u11A8", 2}, +{"\u0903\uAC00", 2}, +{"\u0903\u0308\uAC00", 2}, +{"\u0903\uAC01", 2}, +{"\u0903\u0308\uAC01", 2}, +{"\u0903\u0900", 1}, +{"\u0903\u0308\u0900", 1}, +{"\u0903\u0903", 1}, +{"\u0903\u0308\u0903", 1}, +{"\u0903\u0904", 2}, +{"\u0903\u0308\u0904", 2}, +{"\u0903\u0D4E", 2}, +{"\u0903\u0308\u0D4E", 2}, +{"\u0903\u0915", 2}, +{"\u0903\u0308\u0915", 2}, +{"\u0903\u231A", 2}, +{"\u0903\u0308\u231A", 2}, +{"\u0903\u0300", 1}, +{"\u0903\u0308\u0300", 1}, +{"\u0903\u093C", 1}, +{"\u0903\u0308\u093C", 1}, +{"\u0903\u094D", 1}, +{"\u0903\u0308\u094D", 1}, +{"\u0903\u200D", 1}, +{"\u0903\u0308\u200D", 1}, +{"\u0903\u0378", 2}, +{"\u0903\u0308\u0378", 2}, +{"\u0904\u0020", 2}, +{"\u0904\u0308\u0020", 2}, +{"\u0904\u000D", 2}, +{"\u0904\u0308\u000D", 2}, +{"\u0904\u000A", 2}, +{"\u0904\u0308\u000A", 2}, +{"\u0904\u0001", 2}, +{"\u0904\u0308\u0001", 2}, +{"\u0904\u034F", 1}, +{"\u0904\u0308\u034F", 1}, +{"\u0904\U0001F1E6", 2}, +{"\u0904\u0308\U0001F1E6", 2}, +{"\u0904\u0600", 2}, +{"\u0904\u0308\u0600", 2}, +{"\u0904\u0A03", 1}, +{"\u0904\u0308\u0A03", 1}, +{"\u0904\u1100", 2}, +{"\u0904\u0308\u1100", 2}, +{"\u0904\u1160", 2}, +{"\u0904\u0308\u1160", 2}, +{"\u0904\u11A8", 2}, +{"\u0904\u0308\u11A8", 2}, +{"\u0904\uAC00", 2}, +{"\u0904\u0308\uAC00", 2}, +{"\u0904\uAC01", 2}, +{"\u0904\u0308\uAC01", 2}, +{"\u0904\u0900", 1}, +{"\u0904\u0308\u0900", 1}, +{"\u0904\u0903", 1}, +{"\u0904\u0308\u0903", 1}, +{"\u0904\u0904", 2}, +{"\u0904\u0308\u0904", 2}, +{"\u0904\u0D4E", 2}, +{"\u0904\u0308\u0D4E", 2}, +{"\u0904\u0915", 2}, +{"\u0904\u0308\u0915", 2}, +{"\u0904\u231A", 2}, +{"\u0904\u0308\u231A", 2}, +{"\u0904\u0300", 1}, +{"\u0904\u0308\u0300", 1}, +{"\u0904\u093C", 1}, +{"\u0904\u0308\u093C", 1}, +{"\u0904\u094D", 1}, +{"\u0904\u0308\u094D", 1}, +{"\u0904\u200D", 1}, +{"\u0904\u0308\u200D", 1}, +{"\u0904\u0378", 2}, +{"\u0904\u0308\u0378", 2}, +{"\u0D4E\u0020", 1}, +{"\u0D4E\u0308\u0020", 2}, +{"\u0D4E\u000D", 2}, +{"\u0D4E\u0308\u000D", 2}, +{"\u0D4E\u000A", 2}, +{"\u0D4E\u0308\u000A", 2}, +{"\u0D4E\u0001", 2}, +{"\u0D4E\u0308\u0001", 2}, +{"\u0D4E\u034F", 1}, +{"\u0D4E\u0308\u034F", 1}, +{"\u0D4E\U0001F1E6", 1}, +{"\u0D4E\u0308\U0001F1E6", 2}, +{"\u0D4E\u0600", 1}, +{"\u0D4E\u0308\u0600", 2}, +{"\u0D4E\u0A03", 1}, +{"\u0D4E\u0308\u0A03", 1}, +{"\u0D4E\u1100", 1}, +{"\u0D4E\u0308\u1100", 2}, +{"\u0D4E\u1160", 1}, +{"\u0D4E\u0308\u1160", 2}, +{"\u0D4E\u11A8", 1}, +{"\u0D4E\u0308\u11A8", 2}, +{"\u0D4E\uAC00", 1}, +{"\u0D4E\u0308\uAC00", 2}, +{"\u0D4E\uAC01", 1}, +{"\u0D4E\u0308\uAC01", 2}, +{"\u0D4E\u0900", 1}, +{"\u0D4E\u0308\u0900", 1}, +{"\u0D4E\u0903", 1}, +{"\u0D4E\u0308\u0903", 1}, +{"\u0D4E\u0904", 1}, +{"\u0D4E\u0308\u0904", 2}, +{"\u0D4E\u0D4E", 1}, +{"\u0D4E\u0308\u0D4E", 2}, +{"\u0D4E\u0915", 1}, +{"\u0D4E\u0308\u0915", 2}, +{"\u0D4E\u231A", 1}, +{"\u0D4E\u0308\u231A", 2}, +{"\u0D4E\u0300", 1}, +{"\u0D4E\u0308\u0300", 1}, +{"\u0D4E\u093C", 1}, +{"\u0D4E\u0308\u093C", 1}, +{"\u0D4E\u094D", 1}, +{"\u0D4E\u0308\u094D", 1}, +{"\u0D4E\u200D", 1}, +{"\u0D4E\u0308\u200D", 1}, +{"\u0D4E\u0378", 1}, +{"\u0D4E\u0308\u0378", 2}, +{"\u0915\u0020", 2}, +{"\u0915\u0308\u0020", 2}, +{"\u0915\u000D", 2}, +{"\u0915\u0308\u000D", 2}, +{"\u0915\u000A", 2}, +{"\u0915\u0308\u000A", 2}, +{"\u0915\u0001", 2}, +{"\u0915\u0308\u0001", 2}, +{"\u0915\u034F", 1}, +{"\u0915\u0308\u034F", 1}, +{"\u0915\U0001F1E6", 2}, +{"\u0915\u0308\U0001F1E6", 2}, +{"\u0915\u0600", 2}, +{"\u0915\u0308\u0600", 2}, +{"\u0915\u0A03", 1}, +{"\u0915\u0308\u0A03", 1}, +{"\u0915\u1100", 2}, +{"\u0915\u0308\u1100", 2}, +{"\u0915\u1160", 2}, +{"\u0915\u0308\u1160", 2}, +{"\u0915\u11A8", 2}, +{"\u0915\u0308\u11A8", 2}, +{"\u0915\uAC00", 2}, +{"\u0915\u0308\uAC00", 2}, +{"\u0915\uAC01", 2}, +{"\u0915\u0308\uAC01", 2}, +{"\u0915\u0900", 1}, +{"\u0915\u0308\u0900", 1}, +{"\u0915\u0903", 1}, +{"\u0915\u0308\u0903", 1}, +{"\u0915\u0904", 2}, +{"\u0915\u0308\u0904", 2}, +{"\u0915\u0D4E", 2}, +{"\u0915\u0308\u0D4E", 2}, +{"\u0915\u0915", 2}, +{"\u0915\u0308\u0915", 2}, +{"\u0915\u231A", 2}, +{"\u0915\u0308\u231A", 2}, +{"\u0915\u0300", 1}, +{"\u0915\u0308\u0300", 1}, +{"\u0915\u093C", 1}, +{"\u0915\u0308\u093C", 1}, +{"\u0915\u094D", 1}, +{"\u0915\u0308\u094D", 1}, +{"\u0915\u200D", 1}, +{"\u0915\u0308\u200D", 1}, +{"\u0915\u0378", 2}, +{"\u0915\u0308\u0378", 2}, +{"\u231A\u0020", 2}, +{"\u231A\u0308\u0020", 2}, +{"\u231A\u000D", 2}, +{"\u231A\u0308\u000D", 2}, +{"\u231A\u000A", 2}, +{"\u231A\u0308\u000A", 2}, +{"\u231A\u0001", 2}, +{"\u231A\u0308\u0001", 2}, +{"\u231A\u034F", 1}, +{"\u231A\u0308\u034F", 1}, +{"\u231A\U0001F1E6", 2}, +{"\u231A\u0308\U0001F1E6", 2}, +{"\u231A\u0600", 2}, +{"\u231A\u0308\u0600", 2}, +{"\u231A\u0A03", 1}, +{"\u231A\u0308\u0A03", 1}, +{"\u231A\u1100", 2}, +{"\u231A\u0308\u1100", 2}, +{"\u231A\u1160", 2}, +{"\u231A\u0308\u1160", 2}, +{"\u231A\u11A8", 2}, +{"\u231A\u0308\u11A8", 2}, +{"\u231A\uAC00", 2}, +{"\u231A\u0308\uAC00", 2}, +{"\u231A\uAC01", 2}, +{"\u231A\u0308\uAC01", 2}, +{"\u231A\u0900", 1}, +{"\u231A\u0308\u0900", 1}, +{"\u231A\u0903", 1}, +{"\u231A\u0308\u0903", 1}, +{"\u231A\u0904", 2}, +{"\u231A\u0308\u0904", 2}, +{"\u231A\u0D4E", 2}, +{"\u231A\u0308\u0D4E", 2}, +{"\u231A\u0915", 2}, +{"\u231A\u0308\u0915", 2}, +{"\u231A\u231A", 2}, +{"\u231A\u0308\u231A", 2}, +{"\u231A\u0300", 1}, +{"\u231A\u0308\u0300", 1}, +{"\u231A\u093C", 1}, +{"\u231A\u0308\u093C", 1}, +{"\u231A\u094D", 1}, +{"\u231A\u0308\u094D", 1}, +{"\u231A\u200D", 1}, +{"\u231A\u0308\u200D", 1}, +{"\u231A\u0378", 2}, +{"\u231A\u0308\u0378", 2}, +{"\u0300\u0020", 2}, +{"\u0300\u0308\u0020", 2}, +{"\u0300\u000D", 2}, +{"\u0300\u0308\u000D", 2}, +{"\u0300\u000A", 2}, +{"\u0300\u0308\u000A", 2}, +{"\u0300\u0001", 2}, +{"\u0300\u0308\u0001", 2}, +{"\u0300\u034F", 1}, +{"\u0300\u0308\u034F", 1}, +{"\u0300\U0001F1E6", 2}, +{"\u0300\u0308\U0001F1E6", 2}, +{"\u0300\u0600", 2}, +{"\u0300\u0308\u0600", 2}, +{"\u0300\u0A03", 1}, +{"\u0300\u0308\u0A03", 1}, +{"\u0300\u1100", 2}, +{"\u0300\u0308\u1100", 2}, +{"\u0300\u1160", 2}, +{"\u0300\u0308\u1160", 2}, +{"\u0300\u11A8", 2}, +{"\u0300\u0308\u11A8", 2}, +{"\u0300\uAC00", 2}, +{"\u0300\u0308\uAC00", 2}, +{"\u0300\uAC01", 2}, +{"\u0300\u0308\uAC01", 2}, +{"\u0300\u0900", 1}, +{"\u0300\u0308\u0900", 1}, +{"\u0300\u0903", 1}, +{"\u0300\u0308\u0903", 1}, +{"\u0300\u0904", 2}, +{"\u0300\u0308\u0904", 2}, +{"\u0300\u0D4E", 2}, +{"\u0300\u0308\u0D4E", 2}, +{"\u0300\u0915", 2}, +{"\u0300\u0308\u0915", 2}, +{"\u0300\u231A", 2}, +{"\u0300\u0308\u231A", 2}, +{"\u0300\u0300", 1}, +{"\u0300\u0308\u0300", 1}, +{"\u0300\u093C", 1}, +{"\u0300\u0308\u093C", 1}, +{"\u0300\u094D", 1}, +{"\u0300\u0308\u094D", 1}, +{"\u0300\u200D", 1}, +{"\u0300\u0308\u200D", 1}, +{"\u0300\u0378", 2}, +{"\u0300\u0308\u0378", 2}, +{"\u093C\u0020", 2}, +{"\u093C\u0308\u0020", 2}, +{"\u093C\u000D", 2}, +{"\u093C\u0308\u000D", 2}, +{"\u093C\u000A", 2}, +{"\u093C\u0308\u000A", 2}, +{"\u093C\u0001", 2}, +{"\u093C\u0308\u0001", 2}, +{"\u093C\u034F", 1}, +{"\u093C\u0308\u034F", 1}, +{"\u093C\U0001F1E6", 2}, +{"\u093C\u0308\U0001F1E6", 2}, +{"\u093C\u0600", 2}, +{"\u093C\u0308\u0600", 2}, +{"\u093C\u0A03", 1}, +{"\u093C\u0308\u0A03", 1}, +{"\u093C\u1100", 2}, +{"\u093C\u0308\u1100", 2}, +{"\u093C\u1160", 2}, +{"\u093C\u0308\u1160", 2}, +{"\u093C\u11A8", 2}, +{"\u093C\u0308\u11A8", 2}, +{"\u093C\uAC00", 2}, +{"\u093C\u0308\uAC00", 2}, +{"\u093C\uAC01", 2}, +{"\u093C\u0308\uAC01", 2}, +{"\u093C\u0900", 1}, +{"\u093C\u0308\u0900", 1}, +{"\u093C\u0903", 1}, +{"\u093C\u0308\u0903", 1}, +{"\u093C\u0904", 2}, +{"\u093C\u0308\u0904", 2}, +{"\u093C\u0D4E", 2}, +{"\u093C\u0308\u0D4E", 2}, +{"\u093C\u0915", 2}, +{"\u093C\u0308\u0915", 2}, +{"\u093C\u231A", 2}, +{"\u093C\u0308\u231A", 2}, +{"\u093C\u0300", 1}, +{"\u093C\u0308\u0300", 1}, +{"\u093C\u093C", 1}, +{"\u093C\u0308\u093C", 1}, +{"\u093C\u094D", 1}, +{"\u093C\u0308\u094D", 1}, +{"\u093C\u200D", 1}, +{"\u093C\u0308\u200D", 1}, +{"\u093C\u0378", 2}, +{"\u093C\u0308\u0378", 2}, +{"\u094D\u0020", 2}, +{"\u094D\u0308\u0020", 2}, +{"\u094D\u000D", 2}, +{"\u094D\u0308\u000D", 2}, +{"\u094D\u000A", 2}, +{"\u094D\u0308\u000A", 2}, +{"\u094D\u0001", 2}, +{"\u094D\u0308\u0001", 2}, +{"\u094D\u034F", 1}, +{"\u094D\u0308\u034F", 1}, +{"\u094D\U0001F1E6", 2}, +{"\u094D\u0308\U0001F1E6", 2}, +{"\u094D\u0600", 2}, +{"\u094D\u0308\u0600", 2}, +{"\u094D\u0A03", 1}, +{"\u094D\u0308\u0A03", 1}, +{"\u094D\u1100", 2}, +{"\u094D\u0308\u1100", 2}, +{"\u094D\u1160", 2}, +{"\u094D\u0308\u1160", 2}, +{"\u094D\u11A8", 2}, +{"\u094D\u0308\u11A8", 2}, +{"\u094D\uAC00", 2}, +{"\u094D\u0308\uAC00", 2}, +{"\u094D\uAC01", 2}, +{"\u094D\u0308\uAC01", 2}, +{"\u094D\u0900", 1}, +{"\u094D\u0308\u0900", 1}, +{"\u094D\u0903", 1}, +{"\u094D\u0308\u0903", 1}, +{"\u094D\u0904", 2}, +{"\u094D\u0308\u0904", 2}, +{"\u094D\u0D4E", 2}, +{"\u094D\u0308\u0D4E", 2}, +{"\u094D\u0915", 2}, +{"\u094D\u0308\u0915", 2}, +{"\u094D\u231A", 2}, +{"\u094D\u0308\u231A", 2}, +{"\u094D\u0300", 1}, +{"\u094D\u0308\u0300", 1}, +{"\u094D\u093C", 1}, +{"\u094D\u0308\u093C", 1}, +{"\u094D\u094D", 1}, +{"\u094D\u0308\u094D", 1}, +{"\u094D\u200D", 1}, +{"\u094D\u0308\u200D", 1}, +{"\u094D\u0378", 2}, +{"\u094D\u0308\u0378", 2}, +{"\u200D\u0020", 2}, +{"\u200D\u0308\u0020", 2}, +{"\u200D\u000D", 2}, +{"\u200D\u0308\u000D", 2}, +{"\u200D\u000A", 2}, +{"\u200D\u0308\u000A", 2}, +{"\u200D\u0001", 2}, +{"\u200D\u0308\u0001", 2}, +{"\u200D\u034F", 1}, +{"\u200D\u0308\u034F", 1}, +{"\u200D\U0001F1E6", 2}, +{"\u200D\u0308\U0001F1E6", 2}, +{"\u200D\u0600", 2}, +{"\u200D\u0308\u0600", 2}, +{"\u200D\u0A03", 1}, +{"\u200D\u0308\u0A03", 1}, +{"\u200D\u1100", 2}, +{"\u200D\u0308\u1100", 2}, +{"\u200D\u1160", 2}, +{"\u200D\u0308\u1160", 2}, +{"\u200D\u11A8", 2}, +{"\u200D\u0308\u11A8", 2}, +{"\u200D\uAC00", 2}, +{"\u200D\u0308\uAC00", 2}, +{"\u200D\uAC01", 2}, +{"\u200D\u0308\uAC01", 2}, +{"\u200D\u0900", 1}, +{"\u200D\u0308\u0900", 1}, +{"\u200D\u0903", 1}, +{"\u200D\u0308\u0903", 1}, +{"\u200D\u0904", 2}, +{"\u200D\u0308\u0904", 2}, +{"\u200D\u0D4E", 2}, +{"\u200D\u0308\u0D4E", 2}, +{"\u200D\u0915", 2}, +{"\u200D\u0308\u0915", 2}, +{"\u200D\u231A", 2}, +{"\u200D\u0308\u231A", 2}, +{"\u200D\u0300", 1}, +{"\u200D\u0308\u0300", 1}, +{"\u200D\u093C", 1}, +{"\u200D\u0308\u093C", 1}, +{"\u200D\u094D", 1}, +{"\u200D\u0308\u094D", 1}, +{"\u200D\u200D", 1}, +{"\u200D\u0308\u200D", 1}, +{"\u200D\u0378", 2}, +{"\u200D\u0308\u0378", 2}, +{"\u0378\u0020", 2}, +{"\u0378\u0308\u0020", 2}, +{"\u0378\u000D", 2}, +{"\u0378\u0308\u000D", 2}, +{"\u0378\u000A", 2}, +{"\u0378\u0308\u000A", 2}, +{"\u0378\u0001", 2}, +{"\u0378\u0308\u0001", 2}, +{"\u0378\u034F", 1}, +{"\u0378\u0308\u034F", 1}, +{"\u0378\U0001F1E6", 2}, +{"\u0378\u0308\U0001F1E6", 2}, +{"\u0378\u0600", 2}, +{"\u0378\u0308\u0600", 2}, +{"\u0378\u0A03", 1}, +{"\u0378\u0308\u0A03", 1}, +{"\u0378\u1100", 2}, +{"\u0378\u0308\u1100", 2}, +{"\u0378\u1160", 2}, +{"\u0378\u0308\u1160", 2}, +{"\u0378\u11A8", 2}, +{"\u0378\u0308\u11A8", 2}, +{"\u0378\uAC00", 2}, +{"\u0378\u0308\uAC00", 2}, +{"\u0378\uAC01", 2}, +{"\u0378\u0308\uAC01", 2}, +{"\u0378\u0900", 1}, +{"\u0378\u0308\u0900", 1}, +{"\u0378\u0903", 1}, +{"\u0378\u0308\u0903", 1}, +{"\u0378\u0904", 2}, +{"\u0378\u0308\u0904", 2}, +{"\u0378\u0D4E", 2}, +{"\u0378\u0308\u0D4E", 2}, +{"\u0378\u0915", 2}, +{"\u0378\u0308\u0915", 2}, +{"\u0378\u231A", 2}, +{"\u0378\u0308\u231A", 2}, +{"\u0378\u0300", 1}, +{"\u0378\u0308\u0300", 1}, +{"\u0378\u093C", 1}, +{"\u0378\u0308\u093C", 1}, +{"\u0378\u094D", 1}, +{"\u0378\u0308\u094D", 1}, +{"\u0378\u200D", 1}, +{"\u0378\u0308\u200D", 1}, +{"\u0378\u0378", 2}, +{"\u0378\u0308\u0378", 2}, +{"\u000D\u000A\u0061\u000A\u0308", 4}, +{"\u0061\u0308", 1}, +{"\u0020\u200D\u0646", 2}, +{"\u0646\u200D\u0020", 2}, +{"\u1100\u1100", 1}, +{"\uAC00\u11A8\u1100", 2}, +{"\uAC01\u11A8\u1100", 2}, +{"\U0001F1E6\U0001F1E7\U0001F1E8\u0062", 3}, +{"\u0061\U0001F1E6\U0001F1E7\U0001F1E8\u0062", 4}, +{"\u0061\U0001F1E6\U0001F1E7\u200D\U0001F1E8\u0062", 4}, +{"\u0061\U0001F1E6\u200D\U0001F1E7\U0001F1E8\u0062", 4}, +{"\u0061\U0001F1E6\U0001F1E7\U0001F1E8\U0001F1E9\u0062", 4}, +{"\u0061\u200D", 1}, +{"\u0061\u0308\u0062", 2}, +{"\u0061\u0903\u0062", 2}, +{"\u0061\u0600\u0062", 2}, +{"\U0001F476\U0001F3FF\U0001F476", 2}, +{"\u0061\U0001F3FF\U0001F476", 2}, +{"\u0061\U0001F3FF\U0001F476\u200D\U0001F6D1", 2}, +{"\U0001F476\U0001F3FF\u0308\u200D\U0001F476\U0001F3FF", 1}, +{"\U0001F6D1\u200D\U0001F6D1", 1}, +{"\u0061\u200D\U0001F6D1", 2}, +{"\u2701\u200D\u2701", 1}, +{"\u0061\u200D\u2701", 2}, +{"\u0915\u0924", 2}, +{"\u0915\u094D\u0924", 1}, +{"\u0915\u094D\u094D\u0924", 1}, +{"\u0915\u094D\u200D\u0924", 1}, +{"\u0915\u093C\u200D\u094D\u0924", 1}, +{"\u0915\u093C\u094D\u200D\u0924", 1}, +{"\u0915\u094D\u0924\u094D\u092F", 1}, +{"\u0915\u094D\u0061", 2}, +{"\u0061\u094D\u0924", 2}, +{"\u003F\u094D\u0924", 2}, +{"\u0915\u094D\u094D\u0924", 1}, +} + +// (Note that the test cases below only include the multi-codepoint cases +// listed in the file, as these were the only relevant ones for counting +// grapheme cluster boundaries.) +// +// https://unicode.org/Public/emoji/15.1/emoji-test.txt +// +// emoji-test.txt +// Date: 2023-06-05, 21:39:54 GMT +// © 2023 Unicode®, Inc. +// Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +// For terms of use, see https://www.unicode.org/terms_of_use.html +@(rodata) +official_emoji_test_cases := []Test_Case { +{"\u263A\uFE0F", 1}, +{"\U0001F636\u200D\U0001F32B\uFE0F", 1}, +{"\U0001F636\u200D\U0001F32B", 1}, +{"\U0001F62E\u200D\U0001F4A8", 1}, +{"\U0001F642\u200D\u2194\uFE0F", 1}, +{"\U0001F642\u200D\u2194", 1}, +{"\U0001F642\u200D\u2195\uFE0F", 1}, +{"\U0001F642\u200D\u2195", 1}, +{"\U0001F635\u200D\U0001F4AB", 1}, +{"\u2639\uFE0F", 1}, +{"\u2620\uFE0F", 1}, +{"\u2763\uFE0F", 1}, +{"\u2764\uFE0F\u200D\U0001F525", 1}, +{"\u2764\u200D\U0001F525", 1}, +{"\u2764\uFE0F\u200D\U0001FA79", 1}, +{"\u2764\u200D\U0001FA79", 1}, +{"\u2764\uFE0F", 1}, +{"\U0001F573\uFE0F", 1}, +{"\U0001F441\uFE0F\u200D\U0001F5E8\uFE0F", 1}, +{"\U0001F441\u200D\U0001F5E8\uFE0F", 1}, +{"\U0001F441\uFE0F\u200D\U0001F5E8", 1}, +{"\U0001F441\u200D\U0001F5E8", 1}, +{"\U0001F5E8\uFE0F", 1}, +{"\U0001F5EF\uFE0F", 1}, +{"\U0001F44B\U0001F3FB", 1}, +{"\U0001F44B\U0001F3FC", 1}, +{"\U0001F44B\U0001F3FD", 1}, +{"\U0001F44B\U0001F3FE", 1}, +{"\U0001F44B\U0001F3FF", 1}, +{"\U0001F91A\U0001F3FB", 1}, +{"\U0001F91A\U0001F3FC", 1}, +{"\U0001F91A\U0001F3FD", 1}, +{"\U0001F91A\U0001F3FE", 1}, +{"\U0001F91A\U0001F3FF", 1}, +{"\U0001F590\uFE0F", 1}, +{"\U0001F590\U0001F3FB", 1}, +{"\U0001F590\U0001F3FC", 1}, +{"\U0001F590\U0001F3FD", 1}, +{"\U0001F590\U0001F3FE", 1}, +{"\U0001F590\U0001F3FF", 1}, +{"\u270B\U0001F3FB", 1}, +{"\u270B\U0001F3FC", 1}, +{"\u270B\U0001F3FD", 1}, +{"\u270B\U0001F3FE", 1}, +{"\u270B\U0001F3FF", 1}, +{"\U0001F596\U0001F3FB", 1}, +{"\U0001F596\U0001F3FC", 1}, +{"\U0001F596\U0001F3FD", 1}, +{"\U0001F596\U0001F3FE", 1}, +{"\U0001F596\U0001F3FF", 1}, +{"\U0001FAF1\U0001F3FB", 1}, +{"\U0001FAF1\U0001F3FC", 1}, +{"\U0001FAF1\U0001F3FD", 1}, +{"\U0001FAF1\U0001F3FE", 1}, +{"\U0001FAF1\U0001F3FF", 1}, +{"\U0001FAF2\U0001F3FB", 1}, +{"\U0001FAF2\U0001F3FC", 1}, +{"\U0001FAF2\U0001F3FD", 1}, +{"\U0001FAF2\U0001F3FE", 1}, +{"\U0001FAF2\U0001F3FF", 1}, +{"\U0001FAF3\U0001F3FB", 1}, +{"\U0001FAF3\U0001F3FC", 1}, +{"\U0001FAF3\U0001F3FD", 1}, +{"\U0001FAF3\U0001F3FE", 1}, +{"\U0001FAF3\U0001F3FF", 1}, +{"\U0001FAF4\U0001F3FB", 1}, +{"\U0001FAF4\U0001F3FC", 1}, +{"\U0001FAF4\U0001F3FD", 1}, +{"\U0001FAF4\U0001F3FE", 1}, +{"\U0001FAF4\U0001F3FF", 1}, +{"\U0001FAF7\U0001F3FB", 1}, +{"\U0001FAF7\U0001F3FC", 1}, +{"\U0001FAF7\U0001F3FD", 1}, +{"\U0001FAF7\U0001F3FE", 1}, +{"\U0001FAF7\U0001F3FF", 1}, +{"\U0001FAF8\U0001F3FB", 1}, +{"\U0001FAF8\U0001F3FC", 1}, +{"\U0001FAF8\U0001F3FD", 1}, +{"\U0001FAF8\U0001F3FE", 1}, +{"\U0001FAF8\U0001F3FF", 1}, +{"\U0001F44C\U0001F3FB", 1}, +{"\U0001F44C\U0001F3FC", 1}, +{"\U0001F44C\U0001F3FD", 1}, +{"\U0001F44C\U0001F3FE", 1}, +{"\U0001F44C\U0001F3FF", 1}, +{"\U0001F90C\U0001F3FB", 1}, +{"\U0001F90C\U0001F3FC", 1}, +{"\U0001F90C\U0001F3FD", 1}, +{"\U0001F90C\U0001F3FE", 1}, +{"\U0001F90C\U0001F3FF", 1}, +{"\U0001F90F\U0001F3FB", 1}, +{"\U0001F90F\U0001F3FC", 1}, +{"\U0001F90F\U0001F3FD", 1}, +{"\U0001F90F\U0001F3FE", 1}, +{"\U0001F90F\U0001F3FF", 1}, +{"\u270C\uFE0F", 1}, +{"\u270C\U0001F3FB", 1}, +{"\u270C\U0001F3FC", 1}, +{"\u270C\U0001F3FD", 1}, +{"\u270C\U0001F3FE", 1}, +{"\u270C\U0001F3FF", 1}, +{"\U0001F91E\U0001F3FB", 1}, +{"\U0001F91E\U0001F3FC", 1}, +{"\U0001F91E\U0001F3FD", 1}, +{"\U0001F91E\U0001F3FE", 1}, +{"\U0001F91E\U0001F3FF", 1}, +{"\U0001FAF0\U0001F3FB", 1}, +{"\U0001FAF0\U0001F3FC", 1}, +{"\U0001FAF0\U0001F3FD", 1}, +{"\U0001FAF0\U0001F3FE", 1}, +{"\U0001FAF0\U0001F3FF", 1}, +{"\U0001F91F\U0001F3FB", 1}, +{"\U0001F91F\U0001F3FC", 1}, +{"\U0001F91F\U0001F3FD", 1}, +{"\U0001F91F\U0001F3FE", 1}, +{"\U0001F91F\U0001F3FF", 1}, +{"\U0001F918\U0001F3FB", 1}, +{"\U0001F918\U0001F3FC", 1}, +{"\U0001F918\U0001F3FD", 1}, +{"\U0001F918\U0001F3FE", 1}, +{"\U0001F918\U0001F3FF", 1}, +{"\U0001F919\U0001F3FB", 1}, +{"\U0001F919\U0001F3FC", 1}, +{"\U0001F919\U0001F3FD", 1}, +{"\U0001F919\U0001F3FE", 1}, +{"\U0001F919\U0001F3FF", 1}, +{"\U0001F448\U0001F3FB", 1}, +{"\U0001F448\U0001F3FC", 1}, +{"\U0001F448\U0001F3FD", 1}, +{"\U0001F448\U0001F3FE", 1}, +{"\U0001F448\U0001F3FF", 1}, +{"\U0001F449\U0001F3FB", 1}, +{"\U0001F449\U0001F3FC", 1}, +{"\U0001F449\U0001F3FD", 1}, +{"\U0001F449\U0001F3FE", 1}, +{"\U0001F449\U0001F3FF", 1}, +{"\U0001F446\U0001F3FB", 1}, +{"\U0001F446\U0001F3FC", 1}, +{"\U0001F446\U0001F3FD", 1}, +{"\U0001F446\U0001F3FE", 1}, +{"\U0001F446\U0001F3FF", 1}, +{"\U0001F595\U0001F3FB", 1}, +{"\U0001F595\U0001F3FC", 1}, +{"\U0001F595\U0001F3FD", 1}, +{"\U0001F595\U0001F3FE", 1}, +{"\U0001F595\U0001F3FF", 1}, +{"\U0001F447\U0001F3FB", 1}, +{"\U0001F447\U0001F3FC", 1}, +{"\U0001F447\U0001F3FD", 1}, +{"\U0001F447\U0001F3FE", 1}, +{"\U0001F447\U0001F3FF", 1}, +{"\u261D\uFE0F", 1}, +{"\u261D\U0001F3FB", 1}, +{"\u261D\U0001F3FC", 1}, +{"\u261D\U0001F3FD", 1}, +{"\u261D\U0001F3FE", 1}, +{"\u261D\U0001F3FF", 1}, +{"\U0001FAF5\U0001F3FB", 1}, +{"\U0001FAF5\U0001F3FC", 1}, +{"\U0001FAF5\U0001F3FD", 1}, +{"\U0001FAF5\U0001F3FE", 1}, +{"\U0001FAF5\U0001F3FF", 1}, +{"\U0001F44D\U0001F3FB", 1}, +{"\U0001F44D\U0001F3FC", 1}, +{"\U0001F44D\U0001F3FD", 1}, +{"\U0001F44D\U0001F3FE", 1}, +{"\U0001F44D\U0001F3FF", 1}, +{"\U0001F44E\U0001F3FB", 1}, +{"\U0001F44E\U0001F3FC", 1}, +{"\U0001F44E\U0001F3FD", 1}, +{"\U0001F44E\U0001F3FE", 1}, +{"\U0001F44E\U0001F3FF", 1}, +{"\u270A\U0001F3FB", 1}, +{"\u270A\U0001F3FC", 1}, +{"\u270A\U0001F3FD", 1}, +{"\u270A\U0001F3FE", 1}, +{"\u270A\U0001F3FF", 1}, +{"\U0001F44A\U0001F3FB", 1}, +{"\U0001F44A\U0001F3FC", 1}, +{"\U0001F44A\U0001F3FD", 1}, +{"\U0001F44A\U0001F3FE", 1}, +{"\U0001F44A\U0001F3FF", 1}, +{"\U0001F91B\U0001F3FB", 1}, +{"\U0001F91B\U0001F3FC", 1}, +{"\U0001F91B\U0001F3FD", 1}, +{"\U0001F91B\U0001F3FE", 1}, +{"\U0001F91B\U0001F3FF", 1}, +{"\U0001F91C\U0001F3FB", 1}, +{"\U0001F91C\U0001F3FC", 1}, +{"\U0001F91C\U0001F3FD", 1}, +{"\U0001F91C\U0001F3FE", 1}, +{"\U0001F91C\U0001F3FF", 1}, +{"\U0001F44F\U0001F3FB", 1}, +{"\U0001F44F\U0001F3FC", 1}, +{"\U0001F44F\U0001F3FD", 1}, +{"\U0001F44F\U0001F3FE", 1}, +{"\U0001F44F\U0001F3FF", 1}, +{"\U0001F64C\U0001F3FB", 1}, +{"\U0001F64C\U0001F3FC", 1}, +{"\U0001F64C\U0001F3FD", 1}, +{"\U0001F64C\U0001F3FE", 1}, +{"\U0001F64C\U0001F3FF", 1}, +{"\U0001FAF6\U0001F3FB", 1}, +{"\U0001FAF6\U0001F3FC", 1}, +{"\U0001FAF6\U0001F3FD", 1}, +{"\U0001FAF6\U0001F3FE", 1}, +{"\U0001FAF6\U0001F3FF", 1}, +{"\U0001F450\U0001F3FB", 1}, +{"\U0001F450\U0001F3FC", 1}, +{"\U0001F450\U0001F3FD", 1}, +{"\U0001F450\U0001F3FE", 1}, +{"\U0001F450\U0001F3FF", 1}, +{"\U0001F932\U0001F3FB", 1}, +{"\U0001F932\U0001F3FC", 1}, +{"\U0001F932\U0001F3FD", 1}, +{"\U0001F932\U0001F3FE", 1}, +{"\U0001F932\U0001F3FF", 1}, +{"\U0001F91D\U0001F3FB", 1}, +{"\U0001F91D\U0001F3FC", 1}, +{"\U0001F91D\U0001F3FD", 1}, +{"\U0001F91D\U0001F3FE", 1}, +{"\U0001F91D\U0001F3FF", 1}, +{"\U0001FAF1\U0001F3FB\u200D\U0001FAF2\U0001F3FC", 1}, +{"\U0001FAF1\U0001F3FB\u200D\U0001FAF2\U0001F3FD", 1}, +{"\U0001FAF1\U0001F3FB\u200D\U0001FAF2\U0001F3FE", 1}, +{"\U0001FAF1\U0001F3FB\u200D\U0001FAF2\U0001F3FF", 1}, +{"\U0001FAF1\U0001F3FC\u200D\U0001FAF2\U0001F3FB", 1}, +{"\U0001FAF1\U0001F3FC\u200D\U0001FAF2\U0001F3FD", 1}, +{"\U0001FAF1\U0001F3FC\u200D\U0001FAF2\U0001F3FE", 1}, +{"\U0001FAF1\U0001F3FC\u200D\U0001FAF2\U0001F3FF", 1}, +{"\U0001FAF1\U0001F3FD\u200D\U0001FAF2\U0001F3FB", 1}, +{"\U0001FAF1\U0001F3FD\u200D\U0001FAF2\U0001F3FC", 1}, +{"\U0001FAF1\U0001F3FD\u200D\U0001FAF2\U0001F3FE", 1}, +{"\U0001FAF1\U0001F3FD\u200D\U0001FAF2\U0001F3FF", 1}, +{"\U0001FAF1\U0001F3FE\u200D\U0001FAF2\U0001F3FB", 1}, +{"\U0001FAF1\U0001F3FE\u200D\U0001FAF2\U0001F3FC", 1}, +{"\U0001FAF1\U0001F3FE\u200D\U0001FAF2\U0001F3FD", 1}, +{"\U0001FAF1\U0001F3FE\u200D\U0001FAF2\U0001F3FF", 1}, +{"\U0001FAF1\U0001F3FF\u200D\U0001FAF2\U0001F3FB", 1}, +{"\U0001FAF1\U0001F3FF\u200D\U0001FAF2\U0001F3FC", 1}, +{"\U0001FAF1\U0001F3FF\u200D\U0001FAF2\U0001F3FD", 1}, +{"\U0001FAF1\U0001F3FF\u200D\U0001FAF2\U0001F3FE", 1}, +{"\U0001F64F\U0001F3FB", 1}, +{"\U0001F64F\U0001F3FC", 1}, +{"\U0001F64F\U0001F3FD", 1}, +{"\U0001F64F\U0001F3FE", 1}, +{"\U0001F64F\U0001F3FF", 1}, +{"\u270D\uFE0F", 1}, +{"\u270D\U0001F3FB", 1}, +{"\u270D\U0001F3FC", 1}, +{"\u270D\U0001F3FD", 1}, +{"\u270D\U0001F3FE", 1}, +{"\u270D\U0001F3FF", 1}, +{"\U0001F485\U0001F3FB", 1}, +{"\U0001F485\U0001F3FC", 1}, +{"\U0001F485\U0001F3FD", 1}, +{"\U0001F485\U0001F3FE", 1}, +{"\U0001F485\U0001F3FF", 1}, +{"\U0001F933\U0001F3FB", 1}, +{"\U0001F933\U0001F3FC", 1}, +{"\U0001F933\U0001F3FD", 1}, +{"\U0001F933\U0001F3FE", 1}, +{"\U0001F933\U0001F3FF", 1}, +{"\U0001F4AA\U0001F3FB", 1}, +{"\U0001F4AA\U0001F3FC", 1}, +{"\U0001F4AA\U0001F3FD", 1}, +{"\U0001F4AA\U0001F3FE", 1}, +{"\U0001F4AA\U0001F3FF", 1}, +{"\U0001F9B5\U0001F3FB", 1}, +{"\U0001F9B5\U0001F3FC", 1}, +{"\U0001F9B5\U0001F3FD", 1}, +{"\U0001F9B5\U0001F3FE", 1}, +{"\U0001F9B5\U0001F3FF", 1}, +{"\U0001F9B6\U0001F3FB", 1}, +{"\U0001F9B6\U0001F3FC", 1}, +{"\U0001F9B6\U0001F3FD", 1}, +{"\U0001F9B6\U0001F3FE", 1}, +{"\U0001F9B6\U0001F3FF", 1}, +{"\U0001F442\U0001F3FB", 1}, +{"\U0001F442\U0001F3FC", 1}, +{"\U0001F442\U0001F3FD", 1}, +{"\U0001F442\U0001F3FE", 1}, +{"\U0001F442\U0001F3FF", 1}, +{"\U0001F9BB\U0001F3FB", 1}, +{"\U0001F9BB\U0001F3FC", 1}, +{"\U0001F9BB\U0001F3FD", 1}, +{"\U0001F9BB\U0001F3FE", 1}, +{"\U0001F9BB\U0001F3FF", 1}, +{"\U0001F443\U0001F3FB", 1}, +{"\U0001F443\U0001F3FC", 1}, +{"\U0001F443\U0001F3FD", 1}, +{"\U0001F443\U0001F3FE", 1}, +{"\U0001F443\U0001F3FF", 1}, +{"\U0001F441\uFE0F", 1}, +{"\U0001F476\U0001F3FB", 1}, +{"\U0001F476\U0001F3FC", 1}, +{"\U0001F476\U0001F3FD", 1}, +{"\U0001F476\U0001F3FE", 1}, +{"\U0001F476\U0001F3FF", 1}, +{"\U0001F9D2\U0001F3FB", 1}, +{"\U0001F9D2\U0001F3FC", 1}, +{"\U0001F9D2\U0001F3FD", 1}, +{"\U0001F9D2\U0001F3FE", 1}, +{"\U0001F9D2\U0001F3FF", 1}, +{"\U0001F466\U0001F3FB", 1}, +{"\U0001F466\U0001F3FC", 1}, +{"\U0001F466\U0001F3FD", 1}, +{"\U0001F466\U0001F3FE", 1}, +{"\U0001F466\U0001F3FF", 1}, +{"\U0001F467\U0001F3FB", 1}, +{"\U0001F467\U0001F3FC", 1}, +{"\U0001F467\U0001F3FD", 1}, +{"\U0001F467\U0001F3FE", 1}, +{"\U0001F467\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FF", 1}, +{"\U0001F471\U0001F3FB", 1}, +{"\U0001F471\U0001F3FC", 1}, +{"\U0001F471\U0001F3FD", 1}, +{"\U0001F471\U0001F3FE", 1}, +{"\U0001F471\U0001F3FF", 1}, +{"\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FF", 1}, +{"\U0001F9D4\U0001F3FB", 1}, +{"\U0001F9D4\U0001F3FC", 1}, +{"\U0001F9D4\U0001F3FD", 1}, +{"\U0001F9D4\U0001F3FE", 1}, +{"\U0001F9D4\U0001F3FF", 1}, +{"\U0001F9D4\u200D\u2642\uFE0F", 1}, +{"\U0001F9D4\u200D\u2642", 1}, +{"\U0001F9D4\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F9D4\U0001F3FB\u200D\u2642", 1}, +{"\U0001F9D4\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F9D4\U0001F3FC\u200D\u2642", 1}, +{"\U0001F9D4\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F9D4\U0001F3FD\u200D\u2642", 1}, +{"\U0001F9D4\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F9D4\U0001F3FE\u200D\u2642", 1}, +{"\U0001F9D4\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F9D4\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9D4\u200D\u2640\uFE0F", 1}, +{"\U0001F9D4\u200D\u2640", 1}, +{"\U0001F9D4\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F9D4\U0001F3FB\u200D\u2640", 1}, +{"\U0001F9D4\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F9D4\U0001F3FC\u200D\u2640", 1}, +{"\U0001F9D4\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F9D4\U0001F3FD\u200D\u2640", 1}, +{"\U0001F9D4\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F9D4\U0001F3FE\u200D\u2640", 1}, +{"\U0001F9D4\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F9D4\U0001F3FF\u200D\u2640", 1}, +{"\U0001F468\u200D\U0001F9B0", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F9B0", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F9B0", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F9B0", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F9B0", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F9B0", 1}, +{"\U0001F468\u200D\U0001F9B1", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F9B1", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F9B1", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F9B1", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F9B1", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F9B1", 1}, +{"\U0001F468\u200D\U0001F9B3", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F9B3", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F9B3", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F9B3", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F9B3", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F9B3", 1}, +{"\U0001F468\u200D\U0001F9B2", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F9B2", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F9B2", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F9B2", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F9B2", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F9B2", 1}, +{"\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FF", 1}, +{"\U0001F469\u200D\U0001F9B0", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F9B0", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F9B0", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F9B0", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F9B0", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F9B0", 1}, +{"\U0001F9D1\u200D\U0001F9B0", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F9B0", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F9B0", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F9B0", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F9B0", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F9B0", 1}, +{"\U0001F469\u200D\U0001F9B1", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F9B1", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F9B1", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F9B1", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F9B1", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F9B1", 1}, +{"\U0001F9D1\u200D\U0001F9B1", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F9B1", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F9B1", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F9B1", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F9B1", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F9B1", 1}, +{"\U0001F469\u200D\U0001F9B3", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F9B3", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F9B3", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F9B3", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F9B3", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F9B3", 1}, +{"\U0001F9D1\u200D\U0001F9B3", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F9B3", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F9B3", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F9B3", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F9B3", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F9B3", 1}, +{"\U0001F469\u200D\U0001F9B2", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F9B2", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F9B2", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F9B2", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F9B2", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F9B2", 1}, +{"\U0001F9D1\u200D\U0001F9B2", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F9B2", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F9B2", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F9B2", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F9B2", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F9B2", 1}, +{"\U0001F471\u200D\u2640\uFE0F", 1}, +{"\U0001F471\u200D\u2640", 1}, +{"\U0001F471\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F471\U0001F3FB\u200D\u2640", 1}, +{"\U0001F471\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F471\U0001F3FC\u200D\u2640", 1}, +{"\U0001F471\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F471\U0001F3FD\u200D\u2640", 1}, +{"\U0001F471\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F471\U0001F3FE\u200D\u2640", 1}, +{"\U0001F471\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F471\U0001F3FF\u200D\u2640", 1}, +{"\U0001F471\u200D\u2642\uFE0F", 1}, +{"\U0001F471\u200D\u2642", 1}, +{"\U0001F471\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F471\U0001F3FB\u200D\u2642", 1}, +{"\U0001F471\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F471\U0001F3FC\u200D\u2642", 1}, +{"\U0001F471\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F471\U0001F3FD\u200D\u2642", 1}, +{"\U0001F471\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F471\U0001F3FE\u200D\u2642", 1}, +{"\U0001F471\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F471\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9D3\U0001F3FB", 1}, +{"\U0001F9D3\U0001F3FC", 1}, +{"\U0001F9D3\U0001F3FD", 1}, +{"\U0001F9D3\U0001F3FE", 1}, +{"\U0001F9D3\U0001F3FF", 1}, +{"\U0001F474\U0001F3FB", 1}, +{"\U0001F474\U0001F3FC", 1}, +{"\U0001F474\U0001F3FD", 1}, +{"\U0001F474\U0001F3FE", 1}, +{"\U0001F474\U0001F3FF", 1}, +{"\U0001F475\U0001F3FB", 1}, +{"\U0001F475\U0001F3FC", 1}, +{"\U0001F475\U0001F3FD", 1}, +{"\U0001F475\U0001F3FE", 1}, +{"\U0001F475\U0001F3FF", 1}, +{"\U0001F64D\U0001F3FB", 1}, +{"\U0001F64D\U0001F3FC", 1}, +{"\U0001F64D\U0001F3FD", 1}, +{"\U0001F64D\U0001F3FE", 1}, +{"\U0001F64D\U0001F3FF", 1}, +{"\U0001F64D\u200D\u2642\uFE0F", 1}, +{"\U0001F64D\u200D\u2642", 1}, +{"\U0001F64D\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F64D\U0001F3FB\u200D\u2642", 1}, +{"\U0001F64D\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F64D\U0001F3FC\u200D\u2642", 1}, +{"\U0001F64D\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F64D\U0001F3FD\u200D\u2642", 1}, +{"\U0001F64D\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F64D\U0001F3FE\u200D\u2642", 1}, +{"\U0001F64D\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F64D\U0001F3FF\u200D\u2642", 1}, +{"\U0001F64D\u200D\u2640\uFE0F", 1}, +{"\U0001F64D\u200D\u2640", 1}, +{"\U0001F64D\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F64D\U0001F3FB\u200D\u2640", 1}, +{"\U0001F64D\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F64D\U0001F3FC\u200D\u2640", 1}, +{"\U0001F64D\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F64D\U0001F3FD\u200D\u2640", 1}, +{"\U0001F64D\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F64D\U0001F3FE\u200D\u2640", 1}, +{"\U0001F64D\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F64D\U0001F3FF\u200D\u2640", 1}, +{"\U0001F64E\U0001F3FB", 1}, +{"\U0001F64E\U0001F3FC", 1}, +{"\U0001F64E\U0001F3FD", 1}, +{"\U0001F64E\U0001F3FE", 1}, +{"\U0001F64E\U0001F3FF", 1}, +{"\U0001F64E\u200D\u2642\uFE0F", 1}, +{"\U0001F64E\u200D\u2642", 1}, +{"\U0001F64E\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F64E\U0001F3FB\u200D\u2642", 1}, +{"\U0001F64E\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F64E\U0001F3FC\u200D\u2642", 1}, +{"\U0001F64E\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F64E\U0001F3FD\u200D\u2642", 1}, +{"\U0001F64E\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F64E\U0001F3FE\u200D\u2642", 1}, +{"\U0001F64E\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F64E\U0001F3FF\u200D\u2642", 1}, +{"\U0001F64E\u200D\u2640\uFE0F", 1}, +{"\U0001F64E\u200D\u2640", 1}, +{"\U0001F64E\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F64E\U0001F3FB\u200D\u2640", 1}, +{"\U0001F64E\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F64E\U0001F3FC\u200D\u2640", 1}, +{"\U0001F64E\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F64E\U0001F3FD\u200D\u2640", 1}, +{"\U0001F64E\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F64E\U0001F3FE\u200D\u2640", 1}, +{"\U0001F64E\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F64E\U0001F3FF\u200D\u2640", 1}, +{"\U0001F645\U0001F3FB", 1}, +{"\U0001F645\U0001F3FC", 1}, +{"\U0001F645\U0001F3FD", 1}, +{"\U0001F645\U0001F3FE", 1}, +{"\U0001F645\U0001F3FF", 1}, +{"\U0001F645\u200D\u2642\uFE0F", 1}, +{"\U0001F645\u200D\u2642", 1}, +{"\U0001F645\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F645\U0001F3FB\u200D\u2642", 1}, +{"\U0001F645\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F645\U0001F3FC\u200D\u2642", 1}, +{"\U0001F645\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F645\U0001F3FD\u200D\u2642", 1}, +{"\U0001F645\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F645\U0001F3FE\u200D\u2642", 1}, +{"\U0001F645\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F645\U0001F3FF\u200D\u2642", 1}, +{"\U0001F645\u200D\u2640\uFE0F", 1}, +{"\U0001F645\u200D\u2640", 1}, +{"\U0001F645\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F645\U0001F3FB\u200D\u2640", 1}, +{"\U0001F645\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F645\U0001F3FC\u200D\u2640", 1}, +{"\U0001F645\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F645\U0001F3FD\u200D\u2640", 1}, +{"\U0001F645\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F645\U0001F3FE\u200D\u2640", 1}, +{"\U0001F645\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F645\U0001F3FF\u200D\u2640", 1}, +{"\U0001F646\U0001F3FB", 1}, +{"\U0001F646\U0001F3FC", 1}, +{"\U0001F646\U0001F3FD", 1}, +{"\U0001F646\U0001F3FE", 1}, +{"\U0001F646\U0001F3FF", 1}, +{"\U0001F646\u200D\u2642\uFE0F", 1}, +{"\U0001F646\u200D\u2642", 1}, +{"\U0001F646\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F646\U0001F3FB\u200D\u2642", 1}, +{"\U0001F646\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F646\U0001F3FC\u200D\u2642", 1}, +{"\U0001F646\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F646\U0001F3FD\u200D\u2642", 1}, +{"\U0001F646\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F646\U0001F3FE\u200D\u2642", 1}, +{"\U0001F646\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F646\U0001F3FF\u200D\u2642", 1}, +{"\U0001F646\u200D\u2640\uFE0F", 1}, +{"\U0001F646\u200D\u2640", 1}, +{"\U0001F646\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F646\U0001F3FB\u200D\u2640", 1}, +{"\U0001F646\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F646\U0001F3FC\u200D\u2640", 1}, +{"\U0001F646\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F646\U0001F3FD\u200D\u2640", 1}, +{"\U0001F646\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F646\U0001F3FE\u200D\u2640", 1}, +{"\U0001F646\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F646\U0001F3FF\u200D\u2640", 1}, +{"\U0001F481\U0001F3FB", 1}, +{"\U0001F481\U0001F3FC", 1}, +{"\U0001F481\U0001F3FD", 1}, +{"\U0001F481\U0001F3FE", 1}, +{"\U0001F481\U0001F3FF", 1}, +{"\U0001F481\u200D\u2642\uFE0F", 1}, +{"\U0001F481\u200D\u2642", 1}, +{"\U0001F481\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F481\U0001F3FB\u200D\u2642", 1}, +{"\U0001F481\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F481\U0001F3FC\u200D\u2642", 1}, +{"\U0001F481\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F481\U0001F3FD\u200D\u2642", 1}, +{"\U0001F481\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F481\U0001F3FE\u200D\u2642", 1}, +{"\U0001F481\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F481\U0001F3FF\u200D\u2642", 1}, +{"\U0001F481\u200D\u2640\uFE0F", 1}, +{"\U0001F481\u200D\u2640", 1}, +{"\U0001F481\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F481\U0001F3FB\u200D\u2640", 1}, +{"\U0001F481\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F481\U0001F3FC\u200D\u2640", 1}, +{"\U0001F481\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F481\U0001F3FD\u200D\u2640", 1}, +{"\U0001F481\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F481\U0001F3FE\u200D\u2640", 1}, +{"\U0001F481\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F481\U0001F3FF\u200D\u2640", 1}, +{"\U0001F64B\U0001F3FB", 1}, +{"\U0001F64B\U0001F3FC", 1}, +{"\U0001F64B\U0001F3FD", 1}, +{"\U0001F64B\U0001F3FE", 1}, +{"\U0001F64B\U0001F3FF", 1}, +{"\U0001F64B\u200D\u2642\uFE0F", 1}, +{"\U0001F64B\u200D\u2642", 1}, +{"\U0001F64B\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F64B\U0001F3FB\u200D\u2642", 1}, +{"\U0001F64B\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F64B\U0001F3FC\u200D\u2642", 1}, +{"\U0001F64B\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F64B\U0001F3FD\u200D\u2642", 1}, +{"\U0001F64B\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F64B\U0001F3FE\u200D\u2642", 1}, +{"\U0001F64B\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F64B\U0001F3FF\u200D\u2642", 1}, +{"\U0001F64B\u200D\u2640\uFE0F", 1}, +{"\U0001F64B\u200D\u2640", 1}, +{"\U0001F64B\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F64B\U0001F3FB\u200D\u2640", 1}, +{"\U0001F64B\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F64B\U0001F3FC\u200D\u2640", 1}, +{"\U0001F64B\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F64B\U0001F3FD\u200D\u2640", 1}, +{"\U0001F64B\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F64B\U0001F3FE\u200D\u2640", 1}, +{"\U0001F64B\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F64B\U0001F3FF\u200D\u2640", 1}, +{"\U0001F9CF\U0001F3FB", 1}, +{"\U0001F9CF\U0001F3FC", 1}, +{"\U0001F9CF\U0001F3FD", 1}, +{"\U0001F9CF\U0001F3FE", 1}, +{"\U0001F9CF\U0001F3FF", 1}, +{"\U0001F9CF\u200D\u2642\uFE0F", 1}, +{"\U0001F9CF\u200D\u2642", 1}, +{"\U0001F9CF\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F9CF\U0001F3FB\u200D\u2642", 1}, +{"\U0001F9CF\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F9CF\U0001F3FC\u200D\u2642", 1}, +{"\U0001F9CF\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F9CF\U0001F3FD\u200D\u2642", 1}, +{"\U0001F9CF\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F9CF\U0001F3FE\u200D\u2642", 1}, +{"\U0001F9CF\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F9CF\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9CF\u200D\u2640\uFE0F", 1}, +{"\U0001F9CF\u200D\u2640", 1}, +{"\U0001F9CF\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F9CF\U0001F3FB\u200D\u2640", 1}, +{"\U0001F9CF\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F9CF\U0001F3FC\u200D\u2640", 1}, +{"\U0001F9CF\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F9CF\U0001F3FD\u200D\u2640", 1}, +{"\U0001F9CF\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F9CF\U0001F3FE\u200D\u2640", 1}, +{"\U0001F9CF\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F9CF\U0001F3FF\u200D\u2640", 1}, +{"\U0001F647\U0001F3FB", 1}, +{"\U0001F647\U0001F3FC", 1}, +{"\U0001F647\U0001F3FD", 1}, +{"\U0001F647\U0001F3FE", 1}, +{"\U0001F647\U0001F3FF", 1}, +{"\U0001F647\u200D\u2642\uFE0F", 1}, +{"\U0001F647\u200D\u2642", 1}, +{"\U0001F647\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F647\U0001F3FB\u200D\u2642", 1}, +{"\U0001F647\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F647\U0001F3FC\u200D\u2642", 1}, +{"\U0001F647\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F647\U0001F3FD\u200D\u2642", 1}, +{"\U0001F647\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F647\U0001F3FE\u200D\u2642", 1}, +{"\U0001F647\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F647\U0001F3FF\u200D\u2642", 1}, +{"\U0001F647\u200D\u2640\uFE0F", 1}, +{"\U0001F647\u200D\u2640", 1}, +{"\U0001F647\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F647\U0001F3FB\u200D\u2640", 1}, +{"\U0001F647\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F647\U0001F3FC\u200D\u2640", 1}, +{"\U0001F647\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F647\U0001F3FD\u200D\u2640", 1}, +{"\U0001F647\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F647\U0001F3FE\u200D\u2640", 1}, +{"\U0001F647\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F647\U0001F3FF\u200D\u2640", 1}, +{"\U0001F926\U0001F3FB", 1}, +{"\U0001F926\U0001F3FC", 1}, +{"\U0001F926\U0001F3FD", 1}, +{"\U0001F926\U0001F3FE", 1}, +{"\U0001F926\U0001F3FF", 1}, +{"\U0001F926\u200D\u2642\uFE0F", 1}, +{"\U0001F926\u200D\u2642", 1}, +{"\U0001F926\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F926\U0001F3FB\u200D\u2642", 1}, +{"\U0001F926\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F926\U0001F3FC\u200D\u2642", 1}, +{"\U0001F926\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F926\U0001F3FD\u200D\u2642", 1}, +{"\U0001F926\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F926\U0001F3FE\u200D\u2642", 1}, +{"\U0001F926\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F926\U0001F3FF\u200D\u2642", 1}, +{"\U0001F926\u200D\u2640\uFE0F", 1}, +{"\U0001F926\u200D\u2640", 1}, +{"\U0001F926\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F926\U0001F3FB\u200D\u2640", 1}, +{"\U0001F926\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F926\U0001F3FC\u200D\u2640", 1}, +{"\U0001F926\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F926\U0001F3FD\u200D\u2640", 1}, +{"\U0001F926\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F926\U0001F3FE\u200D\u2640", 1}, +{"\U0001F926\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F926\U0001F3FF\u200D\u2640", 1}, +{"\U0001F937\U0001F3FB", 1}, +{"\U0001F937\U0001F3FC", 1}, +{"\U0001F937\U0001F3FD", 1}, +{"\U0001F937\U0001F3FE", 1}, +{"\U0001F937\U0001F3FF", 1}, +{"\U0001F937\u200D\u2642\uFE0F", 1}, +{"\U0001F937\u200D\u2642", 1}, +{"\U0001F937\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F937\U0001F3FB\u200D\u2642", 1}, +{"\U0001F937\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F937\U0001F3FC\u200D\u2642", 1}, +{"\U0001F937\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F937\U0001F3FD\u200D\u2642", 1}, +{"\U0001F937\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F937\U0001F3FE\u200D\u2642", 1}, +{"\U0001F937\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F937\U0001F3FF\u200D\u2642", 1}, +{"\U0001F937\u200D\u2640\uFE0F", 1}, +{"\U0001F937\u200D\u2640", 1}, +{"\U0001F937\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F937\U0001F3FB\u200D\u2640", 1}, +{"\U0001F937\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F937\U0001F3FC\u200D\u2640", 1}, +{"\U0001F937\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F937\U0001F3FD\u200D\u2640", 1}, +{"\U0001F937\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F937\U0001F3FE\u200D\u2640", 1}, +{"\U0001F937\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F937\U0001F3FF\u200D\u2640", 1}, +{"\U0001F9D1\u200D\u2695\uFE0F", 1}, +{"\U0001F9D1\u200D\u2695", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2695\uFE0F", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2695", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2695\uFE0F", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2695", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2695\uFE0F", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2695", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2695\uFE0F", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2695", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2695\uFE0F", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2695", 1}, +{"\U0001F468\u200D\u2695\uFE0F", 1}, +{"\U0001F468\u200D\u2695", 1}, +{"\U0001F468\U0001F3FB\u200D\u2695\uFE0F", 1}, +{"\U0001F468\U0001F3FB\u200D\u2695", 1}, +{"\U0001F468\U0001F3FC\u200D\u2695\uFE0F", 1}, +{"\U0001F468\U0001F3FC\u200D\u2695", 1}, +{"\U0001F468\U0001F3FD\u200D\u2695\uFE0F", 1}, +{"\U0001F468\U0001F3FD\u200D\u2695", 1}, +{"\U0001F468\U0001F3FE\u200D\u2695\uFE0F", 1}, +{"\U0001F468\U0001F3FE\u200D\u2695", 1}, +{"\U0001F468\U0001F3FF\u200D\u2695\uFE0F", 1}, +{"\U0001F468\U0001F3FF\u200D\u2695", 1}, +{"\U0001F469\u200D\u2695\uFE0F", 1}, +{"\U0001F469\u200D\u2695", 1}, +{"\U0001F469\U0001F3FB\u200D\u2695\uFE0F", 1}, +{"\U0001F469\U0001F3FB\u200D\u2695", 1}, +{"\U0001F469\U0001F3FC\u200D\u2695\uFE0F", 1}, +{"\U0001F469\U0001F3FC\u200D\u2695", 1}, +{"\U0001F469\U0001F3FD\u200D\u2695\uFE0F", 1}, +{"\U0001F469\U0001F3FD\u200D\u2695", 1}, +{"\U0001F469\U0001F3FE\u200D\u2695\uFE0F", 1}, +{"\U0001F469\U0001F3FE\u200D\u2695", 1}, +{"\U0001F469\U0001F3FF\u200D\u2695\uFE0F", 1}, +{"\U0001F469\U0001F3FF\u200D\u2695", 1}, +{"\U0001F9D1\u200D\U0001F393", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F393", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F393", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F393", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F393", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F393", 1}, +{"\U0001F468\u200D\U0001F393", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F393", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F393", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F393", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F393", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F393", 1}, +{"\U0001F469\u200D\U0001F393", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F393", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F393", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F393", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F393", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F393", 1}, +{"\U0001F9D1\u200D\U0001F3EB", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F3EB", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F3EB", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F3EB", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F3EB", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F3EB", 1}, +{"\U0001F468\u200D\U0001F3EB", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F3EB", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F3EB", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F3EB", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F3EB", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F3EB", 1}, +{"\U0001F469\u200D\U0001F3EB", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F3EB", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F3EB", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F3EB", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F3EB", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F3EB", 1}, +{"\U0001F9D1\u200D\u2696\uFE0F", 1}, +{"\U0001F9D1\u200D\u2696", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2696\uFE0F", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2696", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2696\uFE0F", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2696", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2696\uFE0F", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2696", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2696\uFE0F", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2696", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2696\uFE0F", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2696", 1}, +{"\U0001F468\u200D\u2696\uFE0F", 1}, +{"\U0001F468\u200D\u2696", 1}, +{"\U0001F468\U0001F3FB\u200D\u2696\uFE0F", 1}, +{"\U0001F468\U0001F3FB\u200D\u2696", 1}, +{"\U0001F468\U0001F3FC\u200D\u2696\uFE0F", 1}, +{"\U0001F468\U0001F3FC\u200D\u2696", 1}, +{"\U0001F468\U0001F3FD\u200D\u2696\uFE0F", 1}, +{"\U0001F468\U0001F3FD\u200D\u2696", 1}, +{"\U0001F468\U0001F3FE\u200D\u2696\uFE0F", 1}, +{"\U0001F468\U0001F3FE\u200D\u2696", 1}, +{"\U0001F468\U0001F3FF\u200D\u2696\uFE0F", 1}, +{"\U0001F468\U0001F3FF\u200D\u2696", 1}, +{"\U0001F469\u200D\u2696\uFE0F", 1}, +{"\U0001F469\u200D\u2696", 1}, +{"\U0001F469\U0001F3FB\u200D\u2696\uFE0F", 1}, +{"\U0001F469\U0001F3FB\u200D\u2696", 1}, +{"\U0001F469\U0001F3FC\u200D\u2696\uFE0F", 1}, +{"\U0001F469\U0001F3FC\u200D\u2696", 1}, +{"\U0001F469\U0001F3FD\u200D\u2696\uFE0F", 1}, +{"\U0001F469\U0001F3FD\u200D\u2696", 1}, +{"\U0001F469\U0001F3FE\u200D\u2696\uFE0F", 1}, +{"\U0001F469\U0001F3FE\u200D\u2696", 1}, +{"\U0001F469\U0001F3FF\u200D\u2696\uFE0F", 1}, +{"\U0001F469\U0001F3FF\u200D\u2696", 1}, +{"\U0001F9D1\u200D\U0001F33E", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F33E", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F33E", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F33E", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F33E", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F33E", 1}, +{"\U0001F468\u200D\U0001F33E", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F33E", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F33E", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F33E", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F33E", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F33E", 1}, +{"\U0001F469\u200D\U0001F33E", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F33E", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F33E", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F33E", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F33E", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F33E", 1}, +{"\U0001F9D1\u200D\U0001F373", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F373", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F373", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F373", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F373", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F373", 1}, +{"\U0001F468\u200D\U0001F373", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F373", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F373", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F373", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F373", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F373", 1}, +{"\U0001F469\u200D\U0001F373", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F373", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F373", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F373", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F373", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F373", 1}, +{"\U0001F9D1\u200D\U0001F527", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F527", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F527", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F527", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F527", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F527", 1}, +{"\U0001F468\u200D\U0001F527", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F527", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F527", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F527", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F527", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F527", 1}, +{"\U0001F469\u200D\U0001F527", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F527", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F527", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F527", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F527", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F527", 1}, +{"\U0001F9D1\u200D\U0001F3ED", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F3ED", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F3ED", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F3ED", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F3ED", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F3ED", 1}, +{"\U0001F468\u200D\U0001F3ED", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F3ED", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F3ED", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F3ED", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F3ED", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F3ED", 1}, +{"\U0001F469\u200D\U0001F3ED", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F3ED", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F3ED", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F3ED", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F3ED", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F3ED", 1}, +{"\U0001F9D1\u200D\U0001F4BC", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F4BC", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F4BC", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F4BC", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F4BC", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F4BC", 1}, +{"\U0001F468\u200D\U0001F4BC", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F4BC", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F4BC", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F4BC", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F4BC", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F4BC", 1}, +{"\U0001F469\u200D\U0001F4BC", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F4BC", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F4BC", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F4BC", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F4BC", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F4BC", 1}, +{"\U0001F9D1\u200D\U0001F52C", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F52C", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F52C", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F52C", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F52C", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F52C", 1}, +{"\U0001F468\u200D\U0001F52C", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F52C", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F52C", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F52C", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F52C", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F52C", 1}, +{"\U0001F469\u200D\U0001F52C", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F52C", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F52C", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F52C", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F52C", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F52C", 1}, +{"\U0001F9D1\u200D\U0001F4BB", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F4BB", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F4BB", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F4BB", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F4BB", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F4BB", 1}, +{"\U0001F468\u200D\U0001F4BB", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F4BB", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F4BB", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F4BB", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F4BB", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F4BB", 1}, +{"\U0001F469\u200D\U0001F4BB", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F4BB", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F4BB", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F4BB", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F4BB", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F4BB", 1}, +{"\U0001F9D1\u200D\U0001F3A4", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F3A4", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F3A4", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F3A4", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F3A4", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F3A4", 1}, +{"\U0001F468\u200D\U0001F3A4", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F3A4", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F3A4", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F3A4", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F3A4", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F3A4", 1}, +{"\U0001F469\u200D\U0001F3A4", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F3A4", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F3A4", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F3A4", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F3A4", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F3A4", 1}, +{"\U0001F9D1\u200D\U0001F3A8", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F3A8", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F3A8", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F3A8", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F3A8", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F3A8", 1}, +{"\U0001F468\u200D\U0001F3A8", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F3A8", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F3A8", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F3A8", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F3A8", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F3A8", 1}, +{"\U0001F469\u200D\U0001F3A8", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F3A8", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F3A8", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F3A8", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F3A8", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F3A8", 1}, +{"\U0001F9D1\u200D\u2708\uFE0F", 1}, +{"\U0001F9D1\u200D\u2708", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2708\uFE0F", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2708", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2708\uFE0F", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2708", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2708\uFE0F", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2708", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2708\uFE0F", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2708", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2708\uFE0F", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2708", 1}, +{"\U0001F468\u200D\u2708\uFE0F", 1}, +{"\U0001F468\u200D\u2708", 1}, +{"\U0001F468\U0001F3FB\u200D\u2708\uFE0F", 1}, +{"\U0001F468\U0001F3FB\u200D\u2708", 1}, +{"\U0001F468\U0001F3FC\u200D\u2708\uFE0F", 1}, +{"\U0001F468\U0001F3FC\u200D\u2708", 1}, +{"\U0001F468\U0001F3FD\u200D\u2708\uFE0F", 1}, +{"\U0001F468\U0001F3FD\u200D\u2708", 1}, +{"\U0001F468\U0001F3FE\u200D\u2708\uFE0F", 1}, +{"\U0001F468\U0001F3FE\u200D\u2708", 1}, +{"\U0001F468\U0001F3FF\u200D\u2708\uFE0F", 1}, +{"\U0001F468\U0001F3FF\u200D\u2708", 1}, +{"\U0001F469\u200D\u2708\uFE0F", 1}, +{"\U0001F469\u200D\u2708", 1}, +{"\U0001F469\U0001F3FB\u200D\u2708\uFE0F", 1}, +{"\U0001F469\U0001F3FB\u200D\u2708", 1}, +{"\U0001F469\U0001F3FC\u200D\u2708\uFE0F", 1}, +{"\U0001F469\U0001F3FC\u200D\u2708", 1}, +{"\U0001F469\U0001F3FD\u200D\u2708\uFE0F", 1}, +{"\U0001F469\U0001F3FD\u200D\u2708", 1}, +{"\U0001F469\U0001F3FE\u200D\u2708\uFE0F", 1}, +{"\U0001F469\U0001F3FE\u200D\u2708", 1}, +{"\U0001F469\U0001F3FF\u200D\u2708\uFE0F", 1}, +{"\U0001F469\U0001F3FF\u200D\u2708", 1}, +{"\U0001F9D1\u200D\U0001F680", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F680", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F680", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F680", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F680", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F680", 1}, +{"\U0001F468\u200D\U0001F680", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F680", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F680", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F680", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F680", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F680", 1}, +{"\U0001F469\u200D\U0001F680", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F680", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F680", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F680", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F680", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F680", 1}, +{"\U0001F9D1\u200D\U0001F692", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F692", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F692", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F692", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F692", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F692", 1}, +{"\U0001F468\u200D\U0001F692", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F692", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F692", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F692", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F692", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F692", 1}, +{"\U0001F469\u200D\U0001F692", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F692", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F692", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F692", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F692", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F692", 1}, +{"\U0001F46E\U0001F3FB", 1}, +{"\U0001F46E\U0001F3FC", 1}, +{"\U0001F46E\U0001F3FD", 1}, +{"\U0001F46E\U0001F3FE", 1}, +{"\U0001F46E\U0001F3FF", 1}, +{"\U0001F46E\u200D\u2642\uFE0F", 1}, +{"\U0001F46E\u200D\u2642", 1}, +{"\U0001F46E\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F46E\U0001F3FB\u200D\u2642", 1}, +{"\U0001F46E\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F46E\U0001F3FC\u200D\u2642", 1}, +{"\U0001F46E\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F46E\U0001F3FD\u200D\u2642", 1}, +{"\U0001F46E\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F46E\U0001F3FE\u200D\u2642", 1}, +{"\U0001F46E\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F46E\U0001F3FF\u200D\u2642", 1}, +{"\U0001F46E\u200D\u2640\uFE0F", 1}, +{"\U0001F46E\u200D\u2640", 1}, +{"\U0001F46E\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F46E\U0001F3FB\u200D\u2640", 1}, +{"\U0001F46E\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F46E\U0001F3FC\u200D\u2640", 1}, +{"\U0001F46E\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F46E\U0001F3FD\u200D\u2640", 1}, +{"\U0001F46E\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F46E\U0001F3FE\u200D\u2640", 1}, +{"\U0001F46E\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F46E\U0001F3FF\u200D\u2640", 1}, +{"\U0001F575\uFE0F", 1}, +{"\U0001F575\U0001F3FB", 1}, +{"\U0001F575\U0001F3FC", 1}, +{"\U0001F575\U0001F3FD", 1}, +{"\U0001F575\U0001F3FE", 1}, +{"\U0001F575\U0001F3FF", 1}, +{"\U0001F575\uFE0F\u200D\u2642\uFE0F", 1}, +{"\U0001F575\u200D\u2642\uFE0F", 1}, +{"\U0001F575\uFE0F\u200D\u2642", 1}, +{"\U0001F575\u200D\u2642", 1}, +{"\U0001F575\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F575\U0001F3FB\u200D\u2642", 1}, +{"\U0001F575\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F575\U0001F3FC\u200D\u2642", 1}, +{"\U0001F575\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F575\U0001F3FD\u200D\u2642", 1}, +{"\U0001F575\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F575\U0001F3FE\u200D\u2642", 1}, +{"\U0001F575\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F575\U0001F3FF\u200D\u2642", 1}, +{"\U0001F575\uFE0F\u200D\u2640\uFE0F", 1}, +{"\U0001F575\u200D\u2640\uFE0F", 1}, +{"\U0001F575\uFE0F\u200D\u2640", 1}, +{"\U0001F575\u200D\u2640", 1}, +{"\U0001F575\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F575\U0001F3FB\u200D\u2640", 1}, +{"\U0001F575\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F575\U0001F3FC\u200D\u2640", 1}, +{"\U0001F575\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F575\U0001F3FD\u200D\u2640", 1}, +{"\U0001F575\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F575\U0001F3FE\u200D\u2640", 1}, +{"\U0001F575\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F575\U0001F3FF\u200D\u2640", 1}, +{"\U0001F482\U0001F3FB", 1}, +{"\U0001F482\U0001F3FC", 1}, +{"\U0001F482\U0001F3FD", 1}, +{"\U0001F482\U0001F3FE", 1}, +{"\U0001F482\U0001F3FF", 1}, +{"\U0001F482\u200D\u2642\uFE0F", 1}, +{"\U0001F482\u200D\u2642", 1}, +{"\U0001F482\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F482\U0001F3FB\u200D\u2642", 1}, +{"\U0001F482\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F482\U0001F3FC\u200D\u2642", 1}, +{"\U0001F482\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F482\U0001F3FD\u200D\u2642", 1}, +{"\U0001F482\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F482\U0001F3FE\u200D\u2642", 1}, +{"\U0001F482\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F482\U0001F3FF\u200D\u2642", 1}, +{"\U0001F482\u200D\u2640\uFE0F", 1}, +{"\U0001F482\u200D\u2640", 1}, +{"\U0001F482\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F482\U0001F3FB\u200D\u2640", 1}, +{"\U0001F482\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F482\U0001F3FC\u200D\u2640", 1}, +{"\U0001F482\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F482\U0001F3FD\u200D\u2640", 1}, +{"\U0001F482\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F482\U0001F3FE\u200D\u2640", 1}, +{"\U0001F482\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F482\U0001F3FF\u200D\u2640", 1}, +{"\U0001F977\U0001F3FB", 1}, +{"\U0001F977\U0001F3FC", 1}, +{"\U0001F977\U0001F3FD", 1}, +{"\U0001F977\U0001F3FE", 1}, +{"\U0001F977\U0001F3FF", 1}, +{"\U0001F477\U0001F3FB", 1}, +{"\U0001F477\U0001F3FC", 1}, +{"\U0001F477\U0001F3FD", 1}, +{"\U0001F477\U0001F3FE", 1}, +{"\U0001F477\U0001F3FF", 1}, +{"\U0001F477\u200D\u2642\uFE0F", 1}, +{"\U0001F477\u200D\u2642", 1}, +{"\U0001F477\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F477\U0001F3FB\u200D\u2642", 1}, +{"\U0001F477\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F477\U0001F3FC\u200D\u2642", 1}, +{"\U0001F477\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F477\U0001F3FD\u200D\u2642", 1}, +{"\U0001F477\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F477\U0001F3FE\u200D\u2642", 1}, +{"\U0001F477\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F477\U0001F3FF\u200D\u2642", 1}, +{"\U0001F477\u200D\u2640\uFE0F", 1}, +{"\U0001F477\u200D\u2640", 1}, +{"\U0001F477\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F477\U0001F3FB\u200D\u2640", 1}, +{"\U0001F477\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F477\U0001F3FC\u200D\u2640", 1}, +{"\U0001F477\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F477\U0001F3FD\u200D\u2640", 1}, +{"\U0001F477\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F477\U0001F3FE\u200D\u2640", 1}, +{"\U0001F477\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F477\U0001F3FF\u200D\u2640", 1}, +{"\U0001FAC5\U0001F3FB", 1}, +{"\U0001FAC5\U0001F3FC", 1}, +{"\U0001FAC5\U0001F3FD", 1}, +{"\U0001FAC5\U0001F3FE", 1}, +{"\U0001FAC5\U0001F3FF", 1}, +{"\U0001F934\U0001F3FB", 1}, +{"\U0001F934\U0001F3FC", 1}, +{"\U0001F934\U0001F3FD", 1}, +{"\U0001F934\U0001F3FE", 1}, +{"\U0001F934\U0001F3FF", 1}, +{"\U0001F478\U0001F3FB", 1}, +{"\U0001F478\U0001F3FC", 1}, +{"\U0001F478\U0001F3FD", 1}, +{"\U0001F478\U0001F3FE", 1}, +{"\U0001F478\U0001F3FF", 1}, +{"\U0001F473\U0001F3FB", 1}, +{"\U0001F473\U0001F3FC", 1}, +{"\U0001F473\U0001F3FD", 1}, +{"\U0001F473\U0001F3FE", 1}, +{"\U0001F473\U0001F3FF", 1}, +{"\U0001F473\u200D\u2642\uFE0F", 1}, +{"\U0001F473\u200D\u2642", 1}, +{"\U0001F473\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F473\U0001F3FB\u200D\u2642", 1}, +{"\U0001F473\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F473\U0001F3FC\u200D\u2642", 1}, +{"\U0001F473\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F473\U0001F3FD\u200D\u2642", 1}, +{"\U0001F473\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F473\U0001F3FE\u200D\u2642", 1}, +{"\U0001F473\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F473\U0001F3FF\u200D\u2642", 1}, +{"\U0001F473\u200D\u2640\uFE0F", 1}, +{"\U0001F473\u200D\u2640", 1}, +{"\U0001F473\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F473\U0001F3FB\u200D\u2640", 1}, +{"\U0001F473\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F473\U0001F3FC\u200D\u2640", 1}, +{"\U0001F473\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F473\U0001F3FD\u200D\u2640", 1}, +{"\U0001F473\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F473\U0001F3FE\u200D\u2640", 1}, +{"\U0001F473\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F473\U0001F3FF\u200D\u2640", 1}, +{"\U0001F472\U0001F3FB", 1}, +{"\U0001F472\U0001F3FC", 1}, +{"\U0001F472\U0001F3FD", 1}, +{"\U0001F472\U0001F3FE", 1}, +{"\U0001F472\U0001F3FF", 1}, +{"\U0001F9D5\U0001F3FB", 1}, +{"\U0001F9D5\U0001F3FC", 1}, +{"\U0001F9D5\U0001F3FD", 1}, +{"\U0001F9D5\U0001F3FE", 1}, +{"\U0001F9D5\U0001F3FF", 1}, +{"\U0001F935\U0001F3FB", 1}, +{"\U0001F935\U0001F3FC", 1}, +{"\U0001F935\U0001F3FD", 1}, +{"\U0001F935\U0001F3FE", 1}, +{"\U0001F935\U0001F3FF", 1}, +{"\U0001F935\u200D\u2642\uFE0F", 1}, +{"\U0001F935\u200D\u2642", 1}, +{"\U0001F935\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F935\U0001F3FB\u200D\u2642", 1}, +{"\U0001F935\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F935\U0001F3FC\u200D\u2642", 1}, +{"\U0001F935\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F935\U0001F3FD\u200D\u2642", 1}, +{"\U0001F935\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F935\U0001F3FE\u200D\u2642", 1}, +{"\U0001F935\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F935\U0001F3FF\u200D\u2642", 1}, +{"\U0001F935\u200D\u2640\uFE0F", 1}, +{"\U0001F935\u200D\u2640", 1}, +{"\U0001F935\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F935\U0001F3FB\u200D\u2640", 1}, +{"\U0001F935\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F935\U0001F3FC\u200D\u2640", 1}, +{"\U0001F935\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F935\U0001F3FD\u200D\u2640", 1}, +{"\U0001F935\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F935\U0001F3FE\u200D\u2640", 1}, +{"\U0001F935\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F935\U0001F3FF\u200D\u2640", 1}, +{"\U0001F470\U0001F3FB", 1}, +{"\U0001F470\U0001F3FC", 1}, +{"\U0001F470\U0001F3FD", 1}, +{"\U0001F470\U0001F3FE", 1}, +{"\U0001F470\U0001F3FF", 1}, +{"\U0001F470\u200D\u2642\uFE0F", 1}, +{"\U0001F470\u200D\u2642", 1}, +{"\U0001F470\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F470\U0001F3FB\u200D\u2642", 1}, +{"\U0001F470\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F470\U0001F3FC\u200D\u2642", 1}, +{"\U0001F470\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F470\U0001F3FD\u200D\u2642", 1}, +{"\U0001F470\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F470\U0001F3FE\u200D\u2642", 1}, +{"\U0001F470\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F470\U0001F3FF\u200D\u2642", 1}, +{"\U0001F470\u200D\u2640\uFE0F", 1}, +{"\U0001F470\u200D\u2640", 1}, +{"\U0001F470\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F470\U0001F3FB\u200D\u2640", 1}, +{"\U0001F470\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F470\U0001F3FC\u200D\u2640", 1}, +{"\U0001F470\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F470\U0001F3FD\u200D\u2640", 1}, +{"\U0001F470\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F470\U0001F3FE\u200D\u2640", 1}, +{"\U0001F470\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F470\U0001F3FF\u200D\u2640", 1}, +{"\U0001F930\U0001F3FB", 1}, +{"\U0001F930\U0001F3FC", 1}, +{"\U0001F930\U0001F3FD", 1}, +{"\U0001F930\U0001F3FE", 1}, +{"\U0001F930\U0001F3FF", 1}, +{"\U0001FAC3\U0001F3FB", 1}, +{"\U0001FAC3\U0001F3FC", 1}, +{"\U0001FAC3\U0001F3FD", 1}, +{"\U0001FAC3\U0001F3FE", 1}, +{"\U0001FAC3\U0001F3FF", 1}, +{"\U0001FAC4\U0001F3FB", 1}, +{"\U0001FAC4\U0001F3FC", 1}, +{"\U0001FAC4\U0001F3FD", 1}, +{"\U0001FAC4\U0001F3FE", 1}, +{"\U0001FAC4\U0001F3FF", 1}, +{"\U0001F931\U0001F3FB", 1}, +{"\U0001F931\U0001F3FC", 1}, +{"\U0001F931\U0001F3FD", 1}, +{"\U0001F931\U0001F3FE", 1}, +{"\U0001F931\U0001F3FF", 1}, +{"\U0001F469\u200D\U0001F37C", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F37C", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F37C", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F37C", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F37C", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F37C", 1}, +{"\U0001F468\u200D\U0001F37C", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F37C", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F37C", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F37C", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F37C", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F37C", 1}, +{"\U0001F9D1\u200D\U0001F37C", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F37C", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F37C", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F37C", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F37C", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F37C", 1}, +{"\U0001F47C\U0001F3FB", 1}, +{"\U0001F47C\U0001F3FC", 1}, +{"\U0001F47C\U0001F3FD", 1}, +{"\U0001F47C\U0001F3FE", 1}, +{"\U0001F47C\U0001F3FF", 1}, +{"\U0001F385\U0001F3FB", 1}, +{"\U0001F385\U0001F3FC", 1}, +{"\U0001F385\U0001F3FD", 1}, +{"\U0001F385\U0001F3FE", 1}, +{"\U0001F385\U0001F3FF", 1}, +{"\U0001F936\U0001F3FB", 1}, +{"\U0001F936\U0001F3FC", 1}, +{"\U0001F936\U0001F3FD", 1}, +{"\U0001F936\U0001F3FE", 1}, +{"\U0001F936\U0001F3FF", 1}, +{"\U0001F9D1\u200D\U0001F384", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F384", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F384", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F384", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F384", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F384", 1}, +{"\U0001F9B8\U0001F3FB", 1}, +{"\U0001F9B8\U0001F3FC", 1}, +{"\U0001F9B8\U0001F3FD", 1}, +{"\U0001F9B8\U0001F3FE", 1}, +{"\U0001F9B8\U0001F3FF", 1}, +{"\U0001F9B8\u200D\u2642\uFE0F", 1}, +{"\U0001F9B8\u200D\u2642", 1}, +{"\U0001F9B8\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F9B8\U0001F3FB\u200D\u2642", 1}, +{"\U0001F9B8\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F9B8\U0001F3FC\u200D\u2642", 1}, +{"\U0001F9B8\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F9B8\U0001F3FD\u200D\u2642", 1}, +{"\U0001F9B8\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F9B8\U0001F3FE\u200D\u2642", 1}, +{"\U0001F9B8\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F9B8\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9B8\u200D\u2640\uFE0F", 1}, +{"\U0001F9B8\u200D\u2640", 1}, +{"\U0001F9B8\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F9B8\U0001F3FB\u200D\u2640", 1}, +{"\U0001F9B8\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F9B8\U0001F3FC\u200D\u2640", 1}, +{"\U0001F9B8\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F9B8\U0001F3FD\u200D\u2640", 1}, +{"\U0001F9B8\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F9B8\U0001F3FE\u200D\u2640", 1}, +{"\U0001F9B8\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F9B8\U0001F3FF\u200D\u2640", 1}, +{"\U0001F9B9\U0001F3FB", 1}, +{"\U0001F9B9\U0001F3FC", 1}, +{"\U0001F9B9\U0001F3FD", 1}, +{"\U0001F9B9\U0001F3FE", 1}, +{"\U0001F9B9\U0001F3FF", 1}, +{"\U0001F9B9\u200D\u2642\uFE0F", 1}, +{"\U0001F9B9\u200D\u2642", 1}, +{"\U0001F9B9\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F9B9\U0001F3FB\u200D\u2642", 1}, +{"\U0001F9B9\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F9B9\U0001F3FC\u200D\u2642", 1}, +{"\U0001F9B9\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F9B9\U0001F3FD\u200D\u2642", 1}, +{"\U0001F9B9\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F9B9\U0001F3FE\u200D\u2642", 1}, +{"\U0001F9B9\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F9B9\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9B9\u200D\u2640\uFE0F", 1}, +{"\U0001F9B9\u200D\u2640", 1}, +{"\U0001F9B9\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F9B9\U0001F3FB\u200D\u2640", 1}, +{"\U0001F9B9\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F9B9\U0001F3FC\u200D\u2640", 1}, +{"\U0001F9B9\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F9B9\U0001F3FD\u200D\u2640", 1}, +{"\U0001F9B9\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F9B9\U0001F3FE\u200D\u2640", 1}, +{"\U0001F9B9\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F9B9\U0001F3FF\u200D\u2640", 1}, +{"\U0001F9D9\U0001F3FB", 1}, +{"\U0001F9D9\U0001F3FC", 1}, +{"\U0001F9D9\U0001F3FD", 1}, +{"\U0001F9D9\U0001F3FE", 1}, +{"\U0001F9D9\U0001F3FF", 1}, +{"\U0001F9D9\u200D\u2642\uFE0F", 1}, +{"\U0001F9D9\u200D\u2642", 1}, +{"\U0001F9D9\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F9D9\U0001F3FB\u200D\u2642", 1}, +{"\U0001F9D9\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F9D9\U0001F3FC\u200D\u2642", 1}, +{"\U0001F9D9\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F9D9\U0001F3FD\u200D\u2642", 1}, +{"\U0001F9D9\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F9D9\U0001F3FE\u200D\u2642", 1}, +{"\U0001F9D9\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F9D9\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9D9\u200D\u2640\uFE0F", 1}, +{"\U0001F9D9\u200D\u2640", 1}, +{"\U0001F9D9\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F9D9\U0001F3FB\u200D\u2640", 1}, +{"\U0001F9D9\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F9D9\U0001F3FC\u200D\u2640", 1}, +{"\U0001F9D9\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F9D9\U0001F3FD\u200D\u2640", 1}, +{"\U0001F9D9\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F9D9\U0001F3FE\u200D\u2640", 1}, +{"\U0001F9D9\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F9D9\U0001F3FF\u200D\u2640", 1}, +{"\U0001F9DA\U0001F3FB", 1}, +{"\U0001F9DA\U0001F3FC", 1}, +{"\U0001F9DA\U0001F3FD", 1}, +{"\U0001F9DA\U0001F3FE", 1}, +{"\U0001F9DA\U0001F3FF", 1}, +{"\U0001F9DA\u200D\u2642\uFE0F", 1}, +{"\U0001F9DA\u200D\u2642", 1}, +{"\U0001F9DA\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F9DA\U0001F3FB\u200D\u2642", 1}, +{"\U0001F9DA\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F9DA\U0001F3FC\u200D\u2642", 1}, +{"\U0001F9DA\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F9DA\U0001F3FD\u200D\u2642", 1}, +{"\U0001F9DA\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F9DA\U0001F3FE\u200D\u2642", 1}, +{"\U0001F9DA\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F9DA\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9DA\u200D\u2640\uFE0F", 1}, +{"\U0001F9DA\u200D\u2640", 1}, +{"\U0001F9DA\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F9DA\U0001F3FB\u200D\u2640", 1}, +{"\U0001F9DA\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F9DA\U0001F3FC\u200D\u2640", 1}, +{"\U0001F9DA\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F9DA\U0001F3FD\u200D\u2640", 1}, +{"\U0001F9DA\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F9DA\U0001F3FE\u200D\u2640", 1}, +{"\U0001F9DA\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F9DA\U0001F3FF\u200D\u2640", 1}, +{"\U0001F9DB\U0001F3FB", 1}, +{"\U0001F9DB\U0001F3FC", 1}, +{"\U0001F9DB\U0001F3FD", 1}, +{"\U0001F9DB\U0001F3FE", 1}, +{"\U0001F9DB\U0001F3FF", 1}, +{"\U0001F9DB\u200D\u2642\uFE0F", 1}, +{"\U0001F9DB\u200D\u2642", 1}, +{"\U0001F9DB\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F9DB\U0001F3FB\u200D\u2642", 1}, +{"\U0001F9DB\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F9DB\U0001F3FC\u200D\u2642", 1}, +{"\U0001F9DB\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F9DB\U0001F3FD\u200D\u2642", 1}, +{"\U0001F9DB\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F9DB\U0001F3FE\u200D\u2642", 1}, +{"\U0001F9DB\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F9DB\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9DB\u200D\u2640\uFE0F", 1}, +{"\U0001F9DB\u200D\u2640", 1}, +{"\U0001F9DB\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F9DB\U0001F3FB\u200D\u2640", 1}, +{"\U0001F9DB\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F9DB\U0001F3FC\u200D\u2640", 1}, +{"\U0001F9DB\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F9DB\U0001F3FD\u200D\u2640", 1}, +{"\U0001F9DB\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F9DB\U0001F3FE\u200D\u2640", 1}, +{"\U0001F9DB\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F9DB\U0001F3FF\u200D\u2640", 1}, +{"\U0001F9DC\U0001F3FB", 1}, +{"\U0001F9DC\U0001F3FC", 1}, +{"\U0001F9DC\U0001F3FD", 1}, +{"\U0001F9DC\U0001F3FE", 1}, +{"\U0001F9DC\U0001F3FF", 1}, +{"\U0001F9DC\u200D\u2642\uFE0F", 1}, +{"\U0001F9DC\u200D\u2642", 1}, +{"\U0001F9DC\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F9DC\U0001F3FB\u200D\u2642", 1}, +{"\U0001F9DC\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F9DC\U0001F3FC\u200D\u2642", 1}, +{"\U0001F9DC\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F9DC\U0001F3FD\u200D\u2642", 1}, +{"\U0001F9DC\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F9DC\U0001F3FE\u200D\u2642", 1}, +{"\U0001F9DC\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F9DC\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9DC\u200D\u2640\uFE0F", 1}, +{"\U0001F9DC\u200D\u2640", 1}, +{"\U0001F9DC\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F9DC\U0001F3FB\u200D\u2640", 1}, +{"\U0001F9DC\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F9DC\U0001F3FC\u200D\u2640", 1}, +{"\U0001F9DC\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F9DC\U0001F3FD\u200D\u2640", 1}, +{"\U0001F9DC\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F9DC\U0001F3FE\u200D\u2640", 1}, +{"\U0001F9DC\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F9DC\U0001F3FF\u200D\u2640", 1}, +{"\U0001F9DD\U0001F3FB", 1}, +{"\U0001F9DD\U0001F3FC", 1}, +{"\U0001F9DD\U0001F3FD", 1}, +{"\U0001F9DD\U0001F3FE", 1}, +{"\U0001F9DD\U0001F3FF", 1}, +{"\U0001F9DD\u200D\u2642\uFE0F", 1}, +{"\U0001F9DD\u200D\u2642", 1}, +{"\U0001F9DD\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F9DD\U0001F3FB\u200D\u2642", 1}, +{"\U0001F9DD\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F9DD\U0001F3FC\u200D\u2642", 1}, +{"\U0001F9DD\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F9DD\U0001F3FD\u200D\u2642", 1}, +{"\U0001F9DD\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F9DD\U0001F3FE\u200D\u2642", 1}, +{"\U0001F9DD\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F9DD\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9DD\u200D\u2640\uFE0F", 1}, +{"\U0001F9DD\u200D\u2640", 1}, +{"\U0001F9DD\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F9DD\U0001F3FB\u200D\u2640", 1}, +{"\U0001F9DD\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F9DD\U0001F3FC\u200D\u2640", 1}, +{"\U0001F9DD\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F9DD\U0001F3FD\u200D\u2640", 1}, +{"\U0001F9DD\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F9DD\U0001F3FE\u200D\u2640", 1}, +{"\U0001F9DD\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F9DD\U0001F3FF\u200D\u2640", 1}, +{"\U0001F9DE\u200D\u2642\uFE0F", 1}, +{"\U0001F9DE\u200D\u2642", 1}, +{"\U0001F9DE\u200D\u2640\uFE0F", 1}, +{"\U0001F9DE\u200D\u2640", 1}, +{"\U0001F9DF\u200D\u2642\uFE0F", 1}, +{"\U0001F9DF\u200D\u2642", 1}, +{"\U0001F9DF\u200D\u2640\uFE0F", 1}, +{"\U0001F9DF\u200D\u2640", 1}, +{"\U0001F486\U0001F3FB", 1}, +{"\U0001F486\U0001F3FC", 1}, +{"\U0001F486\U0001F3FD", 1}, +{"\U0001F486\U0001F3FE", 1}, +{"\U0001F486\U0001F3FF", 1}, +{"\U0001F486\u200D\u2642\uFE0F", 1}, +{"\U0001F486\u200D\u2642", 1}, +{"\U0001F486\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F486\U0001F3FB\u200D\u2642", 1}, +{"\U0001F486\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F486\U0001F3FC\u200D\u2642", 1}, +{"\U0001F486\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F486\U0001F3FD\u200D\u2642", 1}, +{"\U0001F486\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F486\U0001F3FE\u200D\u2642", 1}, +{"\U0001F486\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F486\U0001F3FF\u200D\u2642", 1}, +{"\U0001F486\u200D\u2640\uFE0F", 1}, +{"\U0001F486\u200D\u2640", 1}, +{"\U0001F486\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F486\U0001F3FB\u200D\u2640", 1}, +{"\U0001F486\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F486\U0001F3FC\u200D\u2640", 1}, +{"\U0001F486\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F486\U0001F3FD\u200D\u2640", 1}, +{"\U0001F486\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F486\U0001F3FE\u200D\u2640", 1}, +{"\U0001F486\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F486\U0001F3FF\u200D\u2640", 1}, +{"\U0001F487\U0001F3FB", 1}, +{"\U0001F487\U0001F3FC", 1}, +{"\U0001F487\U0001F3FD", 1}, +{"\U0001F487\U0001F3FE", 1}, +{"\U0001F487\U0001F3FF", 1}, +{"\U0001F487\u200D\u2642\uFE0F", 1}, +{"\U0001F487\u200D\u2642", 1}, +{"\U0001F487\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F487\U0001F3FB\u200D\u2642", 1}, +{"\U0001F487\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F487\U0001F3FC\u200D\u2642", 1}, +{"\U0001F487\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F487\U0001F3FD\u200D\u2642", 1}, +{"\U0001F487\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F487\U0001F3FE\u200D\u2642", 1}, +{"\U0001F487\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F487\U0001F3FF\u200D\u2642", 1}, +{"\U0001F487\u200D\u2640\uFE0F", 1}, +{"\U0001F487\u200D\u2640", 1}, +{"\U0001F487\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F487\U0001F3FB\u200D\u2640", 1}, +{"\U0001F487\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F487\U0001F3FC\u200D\u2640", 1}, +{"\U0001F487\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F487\U0001F3FD\u200D\u2640", 1}, +{"\U0001F487\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F487\U0001F3FE\u200D\u2640", 1}, +{"\U0001F487\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F487\U0001F3FF\u200D\u2640", 1}, +{"\U0001F6B6\U0001F3FB", 1}, +{"\U0001F6B6\U0001F3FC", 1}, +{"\U0001F6B6\U0001F3FD", 1}, +{"\U0001F6B6\U0001F3FE", 1}, +{"\U0001F6B6\U0001F3FF", 1}, +{"\U0001F6B6\u200D\u2642\uFE0F", 1}, +{"\U0001F6B6\u200D\u2642", 1}, +{"\U0001F6B6\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F6B6\U0001F3FB\u200D\u2642", 1}, +{"\U0001F6B6\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F6B6\U0001F3FC\u200D\u2642", 1}, +{"\U0001F6B6\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F6B6\U0001F3FD\u200D\u2642", 1}, +{"\U0001F6B6\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F6B6\U0001F3FE\u200D\u2642", 1}, +{"\U0001F6B6\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F6B6\U0001F3FF\u200D\u2642", 1}, +{"\U0001F6B6\u200D\u2640\uFE0F", 1}, +{"\U0001F6B6\u200D\u2640", 1}, +{"\U0001F6B6\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F6B6\U0001F3FB\u200D\u2640", 1}, +{"\U0001F6B6\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F6B6\U0001F3FC\u200D\u2640", 1}, +{"\U0001F6B6\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F6B6\U0001F3FD\u200D\u2640", 1}, +{"\U0001F6B6\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F6B6\U0001F3FE\u200D\u2640", 1}, +{"\U0001F6B6\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F6B6\U0001F3FF\u200D\u2640", 1}, +{"\U0001F6B6\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FB\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FB\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FC\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FC\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FD\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FD\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FE\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FE\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FF\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FF\u200D\u27A1", 1}, +{"\U0001F6B6\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F6B6\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FB\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FB\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FB\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FB\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FC\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FC\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FC\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FC\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FD\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FD\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FD\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FD\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FE\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FE\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FE\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FE\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FF\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FF\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FF\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FF\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F6B6\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F6B6\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FB\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FB\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FB\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FB\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FC\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FC\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FC\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FC\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FD\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FD\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FD\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FD\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FE\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FE\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FE\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FE\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FF\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FF\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F6B6\U0001F3FF\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F6B6\U0001F3FF\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F9CD\U0001F3FB", 1}, +{"\U0001F9CD\U0001F3FC", 1}, +{"\U0001F9CD\U0001F3FD", 1}, +{"\U0001F9CD\U0001F3FE", 1}, +{"\U0001F9CD\U0001F3FF", 1}, +{"\U0001F9CD\u200D\u2642\uFE0F", 1}, +{"\U0001F9CD\u200D\u2642", 1}, +{"\U0001F9CD\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F9CD\U0001F3FB\u200D\u2642", 1}, +{"\U0001F9CD\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F9CD\U0001F3FC\u200D\u2642", 1}, +{"\U0001F9CD\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F9CD\U0001F3FD\u200D\u2642", 1}, +{"\U0001F9CD\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F9CD\U0001F3FE\u200D\u2642", 1}, +{"\U0001F9CD\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F9CD\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9CD\u200D\u2640\uFE0F", 1}, +{"\U0001F9CD\u200D\u2640", 1}, +{"\U0001F9CD\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F9CD\U0001F3FB\u200D\u2640", 1}, +{"\U0001F9CD\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F9CD\U0001F3FC\u200D\u2640", 1}, +{"\U0001F9CD\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F9CD\U0001F3FD\u200D\u2640", 1}, +{"\U0001F9CD\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F9CD\U0001F3FE\u200D\u2640", 1}, +{"\U0001F9CD\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F9CD\U0001F3FF\u200D\u2640", 1}, +{"\U0001F9CE\U0001F3FB", 1}, +{"\U0001F9CE\U0001F3FC", 1}, +{"\U0001F9CE\U0001F3FD", 1}, +{"\U0001F9CE\U0001F3FE", 1}, +{"\U0001F9CE\U0001F3FF", 1}, +{"\U0001F9CE\u200D\u2642\uFE0F", 1}, +{"\U0001F9CE\u200D\u2642", 1}, +{"\U0001F9CE\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F9CE\U0001F3FB\u200D\u2642", 1}, +{"\U0001F9CE\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F9CE\U0001F3FC\u200D\u2642", 1}, +{"\U0001F9CE\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F9CE\U0001F3FD\u200D\u2642", 1}, +{"\U0001F9CE\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F9CE\U0001F3FE\u200D\u2642", 1}, +{"\U0001F9CE\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F9CE\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9CE\u200D\u2640\uFE0F", 1}, +{"\U0001F9CE\u200D\u2640", 1}, +{"\U0001F9CE\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F9CE\U0001F3FB\u200D\u2640", 1}, +{"\U0001F9CE\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F9CE\U0001F3FC\u200D\u2640", 1}, +{"\U0001F9CE\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F9CE\U0001F3FD\u200D\u2640", 1}, +{"\U0001F9CE\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F9CE\U0001F3FE\u200D\u2640", 1}, +{"\U0001F9CE\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F9CE\U0001F3FF\u200D\u2640", 1}, +{"\U0001F9CE\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FB\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FB\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FC\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FC\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FD\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FD\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FE\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FE\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FF\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FF\u200D\u27A1", 1}, +{"\U0001F9CE\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F9CE\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FB\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FB\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FB\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FB\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FC\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FC\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FC\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FC\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FD\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FD\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FD\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FD\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FE\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FE\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FE\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FE\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FF\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FF\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FF\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FF\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F9CE\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F9CE\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FB\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FB\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FB\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FB\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FC\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FC\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FC\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FC\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FD\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FD\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FD\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FD\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FE\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FE\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FE\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FE\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FF\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FF\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F9CE\U0001F3FF\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F9CE\U0001F3FF\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F9D1\u200D\U0001F9AF", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F9AF", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F9AF", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F9AF", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F9AF", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F9AF", 1}, +{"\U0001F9D1\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F468\u200D\U0001F9AF", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F9AF", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F9AF", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F9AF", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F9AF", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F9AF", 1}, +{"\U0001F468\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F469\u200D\U0001F9AF", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F9AF", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F9AF", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F9AF", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F9AF", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F9AF", 1}, +{"\U0001F469\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F9AF\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F9AF\u200D\u27A1", 1}, +{"\U0001F9D1\u200D\U0001F9BC", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F9BC", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F9BC", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F9BC", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F9BC", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F9BC", 1}, +{"\U0001F9D1\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F468\u200D\U0001F9BC", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F9BC", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F9BC", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F9BC", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F9BC", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F9BC", 1}, +{"\U0001F468\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F469\u200D\U0001F9BC", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F9BC", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F9BC", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F9BC", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F9BC", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F9BC", 1}, +{"\U0001F469\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F9BC\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F9BC\u200D\u27A1", 1}, +{"\U0001F9D1\u200D\U0001F9BD", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F9BD", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F9BD", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F9BD", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F9BD", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F9BD", 1}, +{"\U0001F9D1\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F468\u200D\U0001F9BD", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F9BD", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F9BD", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F9BD", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F9BD", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F9BD", 1}, +{"\U0001F468\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F469\u200D\U0001F9BD", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F9BD", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F9BD", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F9BD", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F9BD", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F9BD", 1}, +{"\U0001F469\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F9BD\u200D\u27A1\uFE0F", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F9BD\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FB", 1}, +{"\U0001F3C3\U0001F3FC", 1}, +{"\U0001F3C3\U0001F3FD", 1}, +{"\U0001F3C3\U0001F3FE", 1}, +{"\U0001F3C3\U0001F3FF", 1}, +{"\U0001F3C3\u200D\u2642\uFE0F", 1}, +{"\U0001F3C3\u200D\u2642", 1}, +{"\U0001F3C3\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F3C3\U0001F3FB\u200D\u2642", 1}, +{"\U0001F3C3\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F3C3\U0001F3FC\u200D\u2642", 1}, +{"\U0001F3C3\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F3C3\U0001F3FD\u200D\u2642", 1}, +{"\U0001F3C3\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F3C3\U0001F3FE\u200D\u2642", 1}, +{"\U0001F3C3\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F3C3\U0001F3FF\u200D\u2642", 1}, +{"\U0001F3C3\u200D\u2640\uFE0F", 1}, +{"\U0001F3C3\u200D\u2640", 1}, +{"\U0001F3C3\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F3C3\U0001F3FB\u200D\u2640", 1}, +{"\U0001F3C3\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F3C3\U0001F3FC\u200D\u2640", 1}, +{"\U0001F3C3\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F3C3\U0001F3FD\u200D\u2640", 1}, +{"\U0001F3C3\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F3C3\U0001F3FE\u200D\u2640", 1}, +{"\U0001F3C3\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F3C3\U0001F3FF\u200D\u2640", 1}, +{"\U0001F3C3\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FB\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FB\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FC\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FC\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FD\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FD\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FE\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FE\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FF\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FF\u200D\u27A1", 1}, +{"\U0001F3C3\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F3C3\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FB\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FB\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FB\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FB\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FC\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FC\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FC\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FC\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FD\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FD\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FD\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FD\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FE\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FE\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FE\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FE\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FF\u200D\u2640\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FF\u200D\u2640\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FF\u200D\u2640\uFE0F\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FF\u200D\u2640\u200D\u27A1", 1}, +{"\U0001F3C3\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F3C3\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FB\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FB\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FB\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FB\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FC\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FC\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FC\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FC\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FD\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FD\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FD\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FD\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FE\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FE\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FE\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FE\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FF\u200D\u2642\uFE0F\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FF\u200D\u2642\u200D\u27A1\uFE0F", 1}, +{"\U0001F3C3\U0001F3FF\u200D\u2642\uFE0F\u200D\u27A1", 1}, +{"\U0001F3C3\U0001F3FF\u200D\u2642\u200D\u27A1", 1}, +{"\U0001F483\U0001F3FB", 1}, +{"\U0001F483\U0001F3FC", 1}, +{"\U0001F483\U0001F3FD", 1}, +{"\U0001F483\U0001F3FE", 1}, +{"\U0001F483\U0001F3FF", 1}, +{"\U0001F57A\U0001F3FB", 1}, +{"\U0001F57A\U0001F3FC", 1}, +{"\U0001F57A\U0001F3FD", 1}, +{"\U0001F57A\U0001F3FE", 1}, +{"\U0001F57A\U0001F3FF", 1}, +{"\U0001F574\uFE0F", 1}, +{"\U0001F574\U0001F3FB", 1}, +{"\U0001F574\U0001F3FC", 1}, +{"\U0001F574\U0001F3FD", 1}, +{"\U0001F574\U0001F3FE", 1}, +{"\U0001F574\U0001F3FF", 1}, +{"\U0001F46F\u200D\u2642\uFE0F", 1}, +{"\U0001F46F\u200D\u2642", 1}, +{"\U0001F46F\u200D\u2640\uFE0F", 1}, +{"\U0001F46F\u200D\u2640", 1}, +{"\U0001F9D6\U0001F3FB", 1}, +{"\U0001F9D6\U0001F3FC", 1}, +{"\U0001F9D6\U0001F3FD", 1}, +{"\U0001F9D6\U0001F3FE", 1}, +{"\U0001F9D6\U0001F3FF", 1}, +{"\U0001F9D6\u200D\u2642\uFE0F", 1}, +{"\U0001F9D6\u200D\u2642", 1}, +{"\U0001F9D6\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F9D6\U0001F3FB\u200D\u2642", 1}, +{"\U0001F9D6\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F9D6\U0001F3FC\u200D\u2642", 1}, +{"\U0001F9D6\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F9D6\U0001F3FD\u200D\u2642", 1}, +{"\U0001F9D6\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F9D6\U0001F3FE\u200D\u2642", 1}, +{"\U0001F9D6\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F9D6\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9D6\u200D\u2640\uFE0F", 1}, +{"\U0001F9D6\u200D\u2640", 1}, +{"\U0001F9D6\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F9D6\U0001F3FB\u200D\u2640", 1}, +{"\U0001F9D6\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F9D6\U0001F3FC\u200D\u2640", 1}, +{"\U0001F9D6\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F9D6\U0001F3FD\u200D\u2640", 1}, +{"\U0001F9D6\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F9D6\U0001F3FE\u200D\u2640", 1}, +{"\U0001F9D6\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F9D6\U0001F3FF\u200D\u2640", 1}, +{"\U0001F9D7\U0001F3FB", 1}, +{"\U0001F9D7\U0001F3FC", 1}, +{"\U0001F9D7\U0001F3FD", 1}, +{"\U0001F9D7\U0001F3FE", 1}, +{"\U0001F9D7\U0001F3FF", 1}, +{"\U0001F9D7\u200D\u2642\uFE0F", 1}, +{"\U0001F9D7\u200D\u2642", 1}, +{"\U0001F9D7\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F9D7\U0001F3FB\u200D\u2642", 1}, +{"\U0001F9D7\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F9D7\U0001F3FC\u200D\u2642", 1}, +{"\U0001F9D7\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F9D7\U0001F3FD\u200D\u2642", 1}, +{"\U0001F9D7\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F9D7\U0001F3FE\u200D\u2642", 1}, +{"\U0001F9D7\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F9D7\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9D7\u200D\u2640\uFE0F", 1}, +{"\U0001F9D7\u200D\u2640", 1}, +{"\U0001F9D7\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F9D7\U0001F3FB\u200D\u2640", 1}, +{"\U0001F9D7\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F9D7\U0001F3FC\u200D\u2640", 1}, +{"\U0001F9D7\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F9D7\U0001F3FD\u200D\u2640", 1}, +{"\U0001F9D7\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F9D7\U0001F3FE\u200D\u2640", 1}, +{"\U0001F9D7\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F9D7\U0001F3FF\u200D\u2640", 1}, +{"\U0001F3C7\U0001F3FB", 1}, +{"\U0001F3C7\U0001F3FC", 1}, +{"\U0001F3C7\U0001F3FD", 1}, +{"\U0001F3C7\U0001F3FE", 1}, +{"\U0001F3C7\U0001F3FF", 1}, +{"\u26F7\uFE0F", 1}, +{"\U0001F3C2\U0001F3FB", 1}, +{"\U0001F3C2\U0001F3FC", 1}, +{"\U0001F3C2\U0001F3FD", 1}, +{"\U0001F3C2\U0001F3FE", 1}, +{"\U0001F3C2\U0001F3FF", 1}, +{"\U0001F3CC\uFE0F", 1}, +{"\U0001F3CC\U0001F3FB", 1}, +{"\U0001F3CC\U0001F3FC", 1}, +{"\U0001F3CC\U0001F3FD", 1}, +{"\U0001F3CC\U0001F3FE", 1}, +{"\U0001F3CC\U0001F3FF", 1}, +{"\U0001F3CC\uFE0F\u200D\u2642\uFE0F", 1}, +{"\U0001F3CC\u200D\u2642\uFE0F", 1}, +{"\U0001F3CC\uFE0F\u200D\u2642", 1}, +{"\U0001F3CC\u200D\u2642", 1}, +{"\U0001F3CC\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F3CC\U0001F3FB\u200D\u2642", 1}, +{"\U0001F3CC\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F3CC\U0001F3FC\u200D\u2642", 1}, +{"\U0001F3CC\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F3CC\U0001F3FD\u200D\u2642", 1}, +{"\U0001F3CC\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F3CC\U0001F3FE\u200D\u2642", 1}, +{"\U0001F3CC\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F3CC\U0001F3FF\u200D\u2642", 1}, +{"\U0001F3CC\uFE0F\u200D\u2640\uFE0F", 1}, +{"\U0001F3CC\u200D\u2640\uFE0F", 1}, +{"\U0001F3CC\uFE0F\u200D\u2640", 1}, +{"\U0001F3CC\u200D\u2640", 1}, +{"\U0001F3CC\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F3CC\U0001F3FB\u200D\u2640", 1}, +{"\U0001F3CC\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F3CC\U0001F3FC\u200D\u2640", 1}, +{"\U0001F3CC\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F3CC\U0001F3FD\u200D\u2640", 1}, +{"\U0001F3CC\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F3CC\U0001F3FE\u200D\u2640", 1}, +{"\U0001F3CC\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F3CC\U0001F3FF\u200D\u2640", 1}, +{"\U0001F3C4\U0001F3FB", 1}, +{"\U0001F3C4\U0001F3FC", 1}, +{"\U0001F3C4\U0001F3FD", 1}, +{"\U0001F3C4\U0001F3FE", 1}, +{"\U0001F3C4\U0001F3FF", 1}, +{"\U0001F3C4\u200D\u2642\uFE0F", 1}, +{"\U0001F3C4\u200D\u2642", 1}, +{"\U0001F3C4\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F3C4\U0001F3FB\u200D\u2642", 1}, +{"\U0001F3C4\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F3C4\U0001F3FC\u200D\u2642", 1}, +{"\U0001F3C4\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F3C4\U0001F3FD\u200D\u2642", 1}, +{"\U0001F3C4\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F3C4\U0001F3FE\u200D\u2642", 1}, +{"\U0001F3C4\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F3C4\U0001F3FF\u200D\u2642", 1}, +{"\U0001F3C4\u200D\u2640\uFE0F", 1}, +{"\U0001F3C4\u200D\u2640", 1}, +{"\U0001F3C4\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F3C4\U0001F3FB\u200D\u2640", 1}, +{"\U0001F3C4\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F3C4\U0001F3FC\u200D\u2640", 1}, +{"\U0001F3C4\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F3C4\U0001F3FD\u200D\u2640", 1}, +{"\U0001F3C4\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F3C4\U0001F3FE\u200D\u2640", 1}, +{"\U0001F3C4\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F3C4\U0001F3FF\u200D\u2640", 1}, +{"\U0001F6A3\U0001F3FB", 1}, +{"\U0001F6A3\U0001F3FC", 1}, +{"\U0001F6A3\U0001F3FD", 1}, +{"\U0001F6A3\U0001F3FE", 1}, +{"\U0001F6A3\U0001F3FF", 1}, +{"\U0001F6A3\u200D\u2642\uFE0F", 1}, +{"\U0001F6A3\u200D\u2642", 1}, +{"\U0001F6A3\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F6A3\U0001F3FB\u200D\u2642", 1}, +{"\U0001F6A3\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F6A3\U0001F3FC\u200D\u2642", 1}, +{"\U0001F6A3\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F6A3\U0001F3FD\u200D\u2642", 1}, +{"\U0001F6A3\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F6A3\U0001F3FE\u200D\u2642", 1}, +{"\U0001F6A3\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F6A3\U0001F3FF\u200D\u2642", 1}, +{"\U0001F6A3\u200D\u2640\uFE0F", 1}, +{"\U0001F6A3\u200D\u2640", 1}, +{"\U0001F6A3\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F6A3\U0001F3FB\u200D\u2640", 1}, +{"\U0001F6A3\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F6A3\U0001F3FC\u200D\u2640", 1}, +{"\U0001F6A3\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F6A3\U0001F3FD\u200D\u2640", 1}, +{"\U0001F6A3\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F6A3\U0001F3FE\u200D\u2640", 1}, +{"\U0001F6A3\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F6A3\U0001F3FF\u200D\u2640", 1}, +{"\U0001F3CA\U0001F3FB", 1}, +{"\U0001F3CA\U0001F3FC", 1}, +{"\U0001F3CA\U0001F3FD", 1}, +{"\U0001F3CA\U0001F3FE", 1}, +{"\U0001F3CA\U0001F3FF", 1}, +{"\U0001F3CA\u200D\u2642\uFE0F", 1}, +{"\U0001F3CA\u200D\u2642", 1}, +{"\U0001F3CA\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F3CA\U0001F3FB\u200D\u2642", 1}, +{"\U0001F3CA\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F3CA\U0001F3FC\u200D\u2642", 1}, +{"\U0001F3CA\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F3CA\U0001F3FD\u200D\u2642", 1}, +{"\U0001F3CA\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F3CA\U0001F3FE\u200D\u2642", 1}, +{"\U0001F3CA\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F3CA\U0001F3FF\u200D\u2642", 1}, +{"\U0001F3CA\u200D\u2640\uFE0F", 1}, +{"\U0001F3CA\u200D\u2640", 1}, +{"\U0001F3CA\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F3CA\U0001F3FB\u200D\u2640", 1}, +{"\U0001F3CA\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F3CA\U0001F3FC\u200D\u2640", 1}, +{"\U0001F3CA\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F3CA\U0001F3FD\u200D\u2640", 1}, +{"\U0001F3CA\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F3CA\U0001F3FE\u200D\u2640", 1}, +{"\U0001F3CA\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F3CA\U0001F3FF\u200D\u2640", 1}, +{"\u26F9\uFE0F", 1}, +{"\u26F9\U0001F3FB", 1}, +{"\u26F9\U0001F3FC", 1}, +{"\u26F9\U0001F3FD", 1}, +{"\u26F9\U0001F3FE", 1}, +{"\u26F9\U0001F3FF", 1}, +{"\u26F9\uFE0F\u200D\u2642\uFE0F", 1}, +{"\u26F9\u200D\u2642\uFE0F", 1}, +{"\u26F9\uFE0F\u200D\u2642", 1}, +{"\u26F9\u200D\u2642", 1}, +{"\u26F9\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\u26F9\U0001F3FB\u200D\u2642", 1}, +{"\u26F9\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\u26F9\U0001F3FC\u200D\u2642", 1}, +{"\u26F9\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\u26F9\U0001F3FD\u200D\u2642", 1}, +{"\u26F9\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\u26F9\U0001F3FE\u200D\u2642", 1}, +{"\u26F9\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\u26F9\U0001F3FF\u200D\u2642", 1}, +{"\u26F9\uFE0F\u200D\u2640\uFE0F", 1}, +{"\u26F9\u200D\u2640\uFE0F", 1}, +{"\u26F9\uFE0F\u200D\u2640", 1}, +{"\u26F9\u200D\u2640", 1}, +{"\u26F9\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\u26F9\U0001F3FB\u200D\u2640", 1}, +{"\u26F9\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\u26F9\U0001F3FC\u200D\u2640", 1}, +{"\u26F9\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\u26F9\U0001F3FD\u200D\u2640", 1}, +{"\u26F9\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\u26F9\U0001F3FE\u200D\u2640", 1}, +{"\u26F9\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\u26F9\U0001F3FF\u200D\u2640", 1}, +{"\U0001F3CB\uFE0F", 1}, +{"\U0001F3CB\U0001F3FB", 1}, +{"\U0001F3CB\U0001F3FC", 1}, +{"\U0001F3CB\U0001F3FD", 1}, +{"\U0001F3CB\U0001F3FE", 1}, +{"\U0001F3CB\U0001F3FF", 1}, +{"\U0001F3CB\uFE0F\u200D\u2642\uFE0F", 1}, +{"\U0001F3CB\u200D\u2642\uFE0F", 1}, +{"\U0001F3CB\uFE0F\u200D\u2642", 1}, +{"\U0001F3CB\u200D\u2642", 1}, +{"\U0001F3CB\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F3CB\U0001F3FB\u200D\u2642", 1}, +{"\U0001F3CB\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F3CB\U0001F3FC\u200D\u2642", 1}, +{"\U0001F3CB\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F3CB\U0001F3FD\u200D\u2642", 1}, +{"\U0001F3CB\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F3CB\U0001F3FE\u200D\u2642", 1}, +{"\U0001F3CB\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F3CB\U0001F3FF\u200D\u2642", 1}, +{"\U0001F3CB\uFE0F\u200D\u2640\uFE0F", 1}, +{"\U0001F3CB\u200D\u2640\uFE0F", 1}, +{"\U0001F3CB\uFE0F\u200D\u2640", 1}, +{"\U0001F3CB\u200D\u2640", 1}, +{"\U0001F3CB\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F3CB\U0001F3FB\u200D\u2640", 1}, +{"\U0001F3CB\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F3CB\U0001F3FC\u200D\u2640", 1}, +{"\U0001F3CB\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F3CB\U0001F3FD\u200D\u2640", 1}, +{"\U0001F3CB\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F3CB\U0001F3FE\u200D\u2640", 1}, +{"\U0001F3CB\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F3CB\U0001F3FF\u200D\u2640", 1}, +{"\U0001F6B4\U0001F3FB", 1}, +{"\U0001F6B4\U0001F3FC", 1}, +{"\U0001F6B4\U0001F3FD", 1}, +{"\U0001F6B4\U0001F3FE", 1}, +{"\U0001F6B4\U0001F3FF", 1}, +{"\U0001F6B4\u200D\u2642\uFE0F", 1}, +{"\U0001F6B4\u200D\u2642", 1}, +{"\U0001F6B4\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F6B4\U0001F3FB\u200D\u2642", 1}, +{"\U0001F6B4\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F6B4\U0001F3FC\u200D\u2642", 1}, +{"\U0001F6B4\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F6B4\U0001F3FD\u200D\u2642", 1}, +{"\U0001F6B4\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F6B4\U0001F3FE\u200D\u2642", 1}, +{"\U0001F6B4\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F6B4\U0001F3FF\u200D\u2642", 1}, +{"\U0001F6B4\u200D\u2640\uFE0F", 1}, +{"\U0001F6B4\u200D\u2640", 1}, +{"\U0001F6B4\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F6B4\U0001F3FB\u200D\u2640", 1}, +{"\U0001F6B4\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F6B4\U0001F3FC\u200D\u2640", 1}, +{"\U0001F6B4\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F6B4\U0001F3FD\u200D\u2640", 1}, +{"\U0001F6B4\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F6B4\U0001F3FE\u200D\u2640", 1}, +{"\U0001F6B4\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F6B4\U0001F3FF\u200D\u2640", 1}, +{"\U0001F6B5\U0001F3FB", 1}, +{"\U0001F6B5\U0001F3FC", 1}, +{"\U0001F6B5\U0001F3FD", 1}, +{"\U0001F6B5\U0001F3FE", 1}, +{"\U0001F6B5\U0001F3FF", 1}, +{"\U0001F6B5\u200D\u2642\uFE0F", 1}, +{"\U0001F6B5\u200D\u2642", 1}, +{"\U0001F6B5\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F6B5\U0001F3FB\u200D\u2642", 1}, +{"\U0001F6B5\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F6B5\U0001F3FC\u200D\u2642", 1}, +{"\U0001F6B5\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F6B5\U0001F3FD\u200D\u2642", 1}, +{"\U0001F6B5\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F6B5\U0001F3FE\u200D\u2642", 1}, +{"\U0001F6B5\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F6B5\U0001F3FF\u200D\u2642", 1}, +{"\U0001F6B5\u200D\u2640\uFE0F", 1}, +{"\U0001F6B5\u200D\u2640", 1}, +{"\U0001F6B5\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F6B5\U0001F3FB\u200D\u2640", 1}, +{"\U0001F6B5\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F6B5\U0001F3FC\u200D\u2640", 1}, +{"\U0001F6B5\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F6B5\U0001F3FD\u200D\u2640", 1}, +{"\U0001F6B5\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F6B5\U0001F3FE\u200D\u2640", 1}, +{"\U0001F6B5\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F6B5\U0001F3FF\u200D\u2640", 1}, +{"\U0001F938\U0001F3FB", 1}, +{"\U0001F938\U0001F3FC", 1}, +{"\U0001F938\U0001F3FD", 1}, +{"\U0001F938\U0001F3FE", 1}, +{"\U0001F938\U0001F3FF", 1}, +{"\U0001F938\u200D\u2642\uFE0F", 1}, +{"\U0001F938\u200D\u2642", 1}, +{"\U0001F938\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F938\U0001F3FB\u200D\u2642", 1}, +{"\U0001F938\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F938\U0001F3FC\u200D\u2642", 1}, +{"\U0001F938\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F938\U0001F3FD\u200D\u2642", 1}, +{"\U0001F938\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F938\U0001F3FE\u200D\u2642", 1}, +{"\U0001F938\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F938\U0001F3FF\u200D\u2642", 1}, +{"\U0001F938\u200D\u2640\uFE0F", 1}, +{"\U0001F938\u200D\u2640", 1}, +{"\U0001F938\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F938\U0001F3FB\u200D\u2640", 1}, +{"\U0001F938\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F938\U0001F3FC\u200D\u2640", 1}, +{"\U0001F938\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F938\U0001F3FD\u200D\u2640", 1}, +{"\U0001F938\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F938\U0001F3FE\u200D\u2640", 1}, +{"\U0001F938\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F938\U0001F3FF\u200D\u2640", 1}, +{"\U0001F93C\u200D\u2642\uFE0F", 1}, +{"\U0001F93C\u200D\u2642", 1}, +{"\U0001F93C\u200D\u2640\uFE0F", 1}, +{"\U0001F93C\u200D\u2640", 1}, +{"\U0001F93D\U0001F3FB", 1}, +{"\U0001F93D\U0001F3FC", 1}, +{"\U0001F93D\U0001F3FD", 1}, +{"\U0001F93D\U0001F3FE", 1}, +{"\U0001F93D\U0001F3FF", 1}, +{"\U0001F93D\u200D\u2642\uFE0F", 1}, +{"\U0001F93D\u200D\u2642", 1}, +{"\U0001F93D\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F93D\U0001F3FB\u200D\u2642", 1}, +{"\U0001F93D\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F93D\U0001F3FC\u200D\u2642", 1}, +{"\U0001F93D\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F93D\U0001F3FD\u200D\u2642", 1}, +{"\U0001F93D\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F93D\U0001F3FE\u200D\u2642", 1}, +{"\U0001F93D\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F93D\U0001F3FF\u200D\u2642", 1}, +{"\U0001F93D\u200D\u2640\uFE0F", 1}, +{"\U0001F93D\u200D\u2640", 1}, +{"\U0001F93D\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F93D\U0001F3FB\u200D\u2640", 1}, +{"\U0001F93D\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F93D\U0001F3FC\u200D\u2640", 1}, +{"\U0001F93D\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F93D\U0001F3FD\u200D\u2640", 1}, +{"\U0001F93D\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F93D\U0001F3FE\u200D\u2640", 1}, +{"\U0001F93D\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F93D\U0001F3FF\u200D\u2640", 1}, +{"\U0001F93E\U0001F3FB", 1}, +{"\U0001F93E\U0001F3FC", 1}, +{"\U0001F93E\U0001F3FD", 1}, +{"\U0001F93E\U0001F3FE", 1}, +{"\U0001F93E\U0001F3FF", 1}, +{"\U0001F93E\u200D\u2642\uFE0F", 1}, +{"\U0001F93E\u200D\u2642", 1}, +{"\U0001F93E\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F93E\U0001F3FB\u200D\u2642", 1}, +{"\U0001F93E\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F93E\U0001F3FC\u200D\u2642", 1}, +{"\U0001F93E\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F93E\U0001F3FD\u200D\u2642", 1}, +{"\U0001F93E\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F93E\U0001F3FE\u200D\u2642", 1}, +{"\U0001F93E\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F93E\U0001F3FF\u200D\u2642", 1}, +{"\U0001F93E\u200D\u2640\uFE0F", 1}, +{"\U0001F93E\u200D\u2640", 1}, +{"\U0001F93E\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F93E\U0001F3FB\u200D\u2640", 1}, +{"\U0001F93E\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F93E\U0001F3FC\u200D\u2640", 1}, +{"\U0001F93E\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F93E\U0001F3FD\u200D\u2640", 1}, +{"\U0001F93E\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F93E\U0001F3FE\u200D\u2640", 1}, +{"\U0001F93E\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F93E\U0001F3FF\u200D\u2640", 1}, +{"\U0001F939\U0001F3FB", 1}, +{"\U0001F939\U0001F3FC", 1}, +{"\U0001F939\U0001F3FD", 1}, +{"\U0001F939\U0001F3FE", 1}, +{"\U0001F939\U0001F3FF", 1}, +{"\U0001F939\u200D\u2642\uFE0F", 1}, +{"\U0001F939\u200D\u2642", 1}, +{"\U0001F939\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F939\U0001F3FB\u200D\u2642", 1}, +{"\U0001F939\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F939\U0001F3FC\u200D\u2642", 1}, +{"\U0001F939\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F939\U0001F3FD\u200D\u2642", 1}, +{"\U0001F939\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F939\U0001F3FE\u200D\u2642", 1}, +{"\U0001F939\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F939\U0001F3FF\u200D\u2642", 1}, +{"\U0001F939\u200D\u2640\uFE0F", 1}, +{"\U0001F939\u200D\u2640", 1}, +{"\U0001F939\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F939\U0001F3FB\u200D\u2640", 1}, +{"\U0001F939\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F939\U0001F3FC\u200D\u2640", 1}, +{"\U0001F939\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F939\U0001F3FD\u200D\u2640", 1}, +{"\U0001F939\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F939\U0001F3FE\u200D\u2640", 1}, +{"\U0001F939\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F939\U0001F3FF\u200D\u2640", 1}, +{"\U0001F9D8\U0001F3FB", 1}, +{"\U0001F9D8\U0001F3FC", 1}, +{"\U0001F9D8\U0001F3FD", 1}, +{"\U0001F9D8\U0001F3FE", 1}, +{"\U0001F9D8\U0001F3FF", 1}, +{"\U0001F9D8\u200D\u2642\uFE0F", 1}, +{"\U0001F9D8\u200D\u2642", 1}, +{"\U0001F9D8\U0001F3FB\u200D\u2642\uFE0F", 1}, +{"\U0001F9D8\U0001F3FB\u200D\u2642", 1}, +{"\U0001F9D8\U0001F3FC\u200D\u2642\uFE0F", 1}, +{"\U0001F9D8\U0001F3FC\u200D\u2642", 1}, +{"\U0001F9D8\U0001F3FD\u200D\u2642\uFE0F", 1}, +{"\U0001F9D8\U0001F3FD\u200D\u2642", 1}, +{"\U0001F9D8\U0001F3FE\u200D\u2642\uFE0F", 1}, +{"\U0001F9D8\U0001F3FE\u200D\u2642", 1}, +{"\U0001F9D8\U0001F3FF\u200D\u2642\uFE0F", 1}, +{"\U0001F9D8\U0001F3FF\u200D\u2642", 1}, +{"\U0001F9D8\u200D\u2640\uFE0F", 1}, +{"\U0001F9D8\u200D\u2640", 1}, +{"\U0001F9D8\U0001F3FB\u200D\u2640\uFE0F", 1}, +{"\U0001F9D8\U0001F3FB\u200D\u2640", 1}, +{"\U0001F9D8\U0001F3FC\u200D\u2640\uFE0F", 1}, +{"\U0001F9D8\U0001F3FC\u200D\u2640", 1}, +{"\U0001F9D8\U0001F3FD\u200D\u2640\uFE0F", 1}, +{"\U0001F9D8\U0001F3FD\u200D\u2640", 1}, +{"\U0001F9D8\U0001F3FE\u200D\u2640\uFE0F", 1}, +{"\U0001F9D8\U0001F3FE\u200D\u2640", 1}, +{"\U0001F9D8\U0001F3FF\u200D\u2640\uFE0F", 1}, +{"\U0001F9D8\U0001F3FF\u200D\u2640", 1}, +{"\U0001F6C0\U0001F3FB", 1}, +{"\U0001F6C0\U0001F3FC", 1}, +{"\U0001F6C0\U0001F3FD", 1}, +{"\U0001F6C0\U0001F3FE", 1}, +{"\U0001F6C0\U0001F3FF", 1}, +{"\U0001F6CC\U0001F3FB", 1}, +{"\U0001F6CC\U0001F3FC", 1}, +{"\U0001F6CC\U0001F3FD", 1}, +{"\U0001F6CC\U0001F3FE", 1}, +{"\U0001F6CC\U0001F3FF", 1}, +{"\U0001F9D1\u200D\U0001F91D\u200D\U0001F9D1", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FB\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FC\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FD\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FE\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FF\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F46D\U0001F3FB", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F91D\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F91D\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F91D\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F91D\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F91D\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F46D\U0001F3FC", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F91D\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F91D\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F91D\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F91D\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F91D\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F46D\U0001F3FD", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F91D\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F91D\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F91D\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F91D\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F91D\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F46D\U0001F3FE", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F91D\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F91D\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F91D\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F91D\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F91D\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F46D\U0001F3FF", 1}, +{"\U0001F46B\U0001F3FB", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F91D\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F91D\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F91D\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FB\u200D\U0001F91D\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F91D\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F46B\U0001F3FC", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F91D\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F91D\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FC\u200D\U0001F91D\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F91D\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F91D\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F46B\U0001F3FD", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F91D\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FD\u200D\U0001F91D\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F91D\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F91D\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F91D\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F46B\U0001F3FE", 1}, +{"\U0001F469\U0001F3FE\u200D\U0001F91D\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F91D\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F91D\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F91D\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FF\u200D\U0001F91D\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F46B\U0001F3FF", 1}, +{"\U0001F46C\U0001F3FB", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F91D\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F91D\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F91D\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FB\u200D\U0001F91D\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F91D\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F46C\U0001F3FC", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F91D\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F91D\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FC\u200D\U0001F91D\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F91D\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F91D\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F46C\U0001F3FD", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F91D\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FD\u200D\U0001F91D\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F91D\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F91D\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F91D\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F46C\U0001F3FE", 1}, +{"\U0001F468\U0001F3FE\u200D\U0001F91D\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F91D\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F91D\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F91D\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FF\u200D\U0001F91D\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F46C\U0001F3FF", 1}, +{"\U0001F48F\U0001F3FB", 1}, +{"\U0001F48F\U0001F3FC", 1}, +{"\U0001F48F\U0001F3FD", 1}, +{"\U0001F48F\U0001F3FE", 1}, +{"\U0001F48F\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F469\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468", 1}, +{"\U0001F469\u200D\u2764\u200D\U0001F48B\u200D\U0001F468", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468", 1}, +{"\U0001F468\u200D\u2764\u200D\U0001F48B\u200D\U0001F468", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469", 1}, +{"\U0001F469\u200D\u2764\u200D\U0001F48B\u200D\U0001F469", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F48B\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F491\U0001F3FB", 1}, +{"\U0001F491\U0001F3FC", 1}, +{"\U0001F491\U0001F3FD", 1}, +{"\U0001F491\U0001F3FE", 1}, +{"\U0001F491\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FB\u200D\u2764\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FC\u200D\u2764\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FD\u200D\u2764\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FE\u200D\u2764\u200D\U0001F9D1\U0001F3FF", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\u200D\U0001F9D1\U0001F3FB", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\u200D\U0001F9D1\U0001F3FC", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\u200D\U0001F9D1\U0001F3FD", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F9D1\U0001F3FF\u200D\u2764\u200D\U0001F9D1\U0001F3FE", 1}, +{"\U0001F469\u200D\u2764\uFE0F\u200D\U0001F468", 1}, +{"\U0001F469\u200D\u2764\u200D\U0001F468", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\u200D\u2764\uFE0F\u200D\U0001F468", 1}, +{"\U0001F468\u200D\u2764\u200D\U0001F468", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FB\u200D\u2764\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FC\u200D\u2764\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FD\u200D\u2764\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FE\u200D\u2764\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\u200D\U0001F468\U0001F3FB", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\u200D\U0001F468\U0001F3FC", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\u200D\U0001F468\U0001F3FD", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\u200D\U0001F468\U0001F3FE", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F468\U0001F3FF\u200D\u2764\u200D\U0001F468\U0001F3FF", 1}, +{"\U0001F469\u200D\u2764\uFE0F\u200D\U0001F469", 1}, +{"\U0001F469\u200D\u2764\u200D\U0001F469", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FB\u200D\u2764\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FC\u200D\u2764\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FD\u200D\u2764\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FE\u200D\u2764\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F469\U0001F3FB", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F469\U0001F3FC", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F469\U0001F3FD", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F469\U0001F3FE", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\uFE0F\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F469\U0001F3FF\u200D\u2764\u200D\U0001F469\U0001F3FF", 1}, +{"\U0001F468\u200D\U0001F469\u200D\U0001F466", 1}, +{"\U0001F468\u200D\U0001F469\u200D\U0001F467", 1}, +{"\U0001F468\u200D\U0001F469\u200D\U0001F467\u200D\U0001F466", 1}, +{"\U0001F468\u200D\U0001F469\u200D\U0001F466\u200D\U0001F466", 1}, +{"\U0001F468\u200D\U0001F469\u200D\U0001F467\u200D\U0001F467", 1}, +{"\U0001F468\u200D\U0001F468\u200D\U0001F466", 1}, +{"\U0001F468\u200D\U0001F468\u200D\U0001F467", 1}, +{"\U0001F468\u200D\U0001F468\u200D\U0001F467\u200D\U0001F466", 1}, +{"\U0001F468\u200D\U0001F468\u200D\U0001F466\u200D\U0001F466", 1}, +{"\U0001F468\u200D\U0001F468\u200D\U0001F467\u200D\U0001F467", 1}, +{"\U0001F469\u200D\U0001F469\u200D\U0001F466", 1}, +{"\U0001F469\u200D\U0001F469\u200D\U0001F467", 1}, +{"\U0001F469\u200D\U0001F469\u200D\U0001F467\u200D\U0001F466", 1}, +{"\U0001F469\u200D\U0001F469\u200D\U0001F466\u200D\U0001F466", 1}, +{"\U0001F469\u200D\U0001F469\u200D\U0001F467\u200D\U0001F467", 1}, +{"\U0001F468\u200D\U0001F466", 1}, +{"\U0001F468\u200D\U0001F466\u200D\U0001F466", 1}, +{"\U0001F468\u200D\U0001F467", 1}, +{"\U0001F468\u200D\U0001F467\u200D\U0001F466", 1}, +{"\U0001F468\u200D\U0001F467\u200D\U0001F467", 1}, +{"\U0001F469\u200D\U0001F466", 1}, +{"\U0001F469\u200D\U0001F466\u200D\U0001F466", 1}, +{"\U0001F469\u200D\U0001F467", 1}, +{"\U0001F469\u200D\U0001F467\u200D\U0001F466", 1}, +{"\U0001F469\u200D\U0001F467\u200D\U0001F467", 1}, +{"\U0001F5E3\uFE0F", 1}, +{"\U0001F9D1\u200D\U0001F9D1\u200D\U0001F9D2", 1}, +{"\U0001F9D1\u200D\U0001F9D1\u200D\U0001F9D2\u200D\U0001F9D2", 1}, +{"\U0001F9D1\u200D\U0001F9D2", 1}, +{"\U0001F9D1\u200D\U0001F9D2\u200D\U0001F9D2", 1}, +{"\U0001F415\u200D\U0001F9BA", 1}, +{"\U0001F408\u200D\u2B1B", 1}, +{"\U0001F43F\uFE0F", 1}, +{"\U0001F43B\u200D\u2744\uFE0F", 1}, +{"\U0001F43B\u200D\u2744", 1}, +{"\U0001F54A\uFE0F", 1}, +{"\U0001F426\u200D\u2B1B", 1}, +{"\U0001F426\u200D\U0001F525", 1}, +{"\U0001F577\uFE0F", 1}, +{"\U0001F578\uFE0F", 1}, +{"\U0001F3F5\uFE0F", 1}, +{"\u2618\uFE0F", 1}, +{"\U0001F34B\u200D\U0001F7E9", 1}, +{"\U0001F336\uFE0F", 1}, +{"\U0001F344\u200D\U0001F7EB", 1}, +{"\U0001F37D\uFE0F", 1}, +{"\U0001F5FA\uFE0F", 1}, +{"\U0001F3D4\uFE0F", 1}, +{"\u26F0\uFE0F", 1}, +{"\U0001F3D5\uFE0F", 1}, +{"\U0001F3D6\uFE0F", 1}, +{"\U0001F3DC\uFE0F", 1}, +{"\U0001F3DD\uFE0F", 1}, +{"\U0001F3DE\uFE0F", 1}, +{"\U0001F3DF\uFE0F", 1}, +{"\U0001F3DB\uFE0F", 1}, +{"\U0001F3D7\uFE0F", 1}, +{"\U0001F3D8\uFE0F", 1}, +{"\U0001F3DA\uFE0F", 1}, +{"\u26E9\uFE0F", 1}, +{"\U0001F3D9\uFE0F", 1}, +{"\u2668\uFE0F", 1}, +{"\U0001F3CE\uFE0F", 1}, +{"\U0001F3CD\uFE0F", 1}, +{"\U0001F6E3\uFE0F", 1}, +{"\U0001F6E4\uFE0F", 1}, +{"\U0001F6E2\uFE0F", 1}, +{"\U0001F6F3\uFE0F", 1}, +{"\u26F4\uFE0F", 1}, +{"\U0001F6E5\uFE0F", 1}, +{"\u2708\uFE0F", 1}, +{"\U0001F6E9\uFE0F", 1}, +{"\U0001F6F0\uFE0F", 1}, +{"\U0001F6CE\uFE0F", 1}, +{"\u23F1\uFE0F", 1}, +{"\u23F2\uFE0F", 1}, +{"\U0001F570\uFE0F", 1}, +{"\U0001F321\uFE0F", 1}, +{"\u2600\uFE0F", 1}, +{"\u2601\uFE0F", 1}, +{"\u26C8\uFE0F", 1}, +{"\U0001F324\uFE0F", 1}, +{"\U0001F325\uFE0F", 1}, +{"\U0001F326\uFE0F", 1}, +{"\U0001F327\uFE0F", 1}, +{"\U0001F328\uFE0F", 1}, +{"\U0001F329\uFE0F", 1}, +{"\U0001F32A\uFE0F", 1}, +{"\U0001F32B\uFE0F", 1}, +{"\U0001F32C\uFE0F", 1}, +{"\u2602\uFE0F", 1}, +{"\u26F1\uFE0F", 1}, +{"\u2744\uFE0F", 1}, +{"\u2603\uFE0F", 1}, +{"\u2604\uFE0F", 1}, +{"\U0001F397\uFE0F", 1}, +{"\U0001F39F\uFE0F", 1}, +{"\U0001F396\uFE0F", 1}, +{"\u26F8\uFE0F", 1}, +{"\U0001F579\uFE0F", 1}, +{"\u2660\uFE0F", 1}, +{"\u2665\uFE0F", 1}, +{"\u2666\uFE0F", 1}, +{"\u2663\uFE0F", 1}, +{"\u265F\uFE0F", 1}, +{"\U0001F5BC\uFE0F", 1}, +{"\U0001F576\uFE0F", 1}, +{"\U0001F6CD\uFE0F", 1}, +{"\u26D1\uFE0F", 1}, +{"\U0001F399\uFE0F", 1}, +{"\U0001F39A\uFE0F", 1}, +{"\U0001F39B\uFE0F", 1}, +{"\u260E\uFE0F", 1}, +{"\U0001F5A5\uFE0F", 1}, +{"\U0001F5A8\uFE0F", 1}, +{"\u2328\uFE0F", 1}, +{"\U0001F5B1\uFE0F", 1}, +{"\U0001F5B2\uFE0F", 1}, +{"\U0001F39E\uFE0F", 1}, +{"\U0001F4FD\uFE0F", 1}, +{"\U0001F56F\uFE0F", 1}, +{"\U0001F5DE\uFE0F", 1}, +{"\U0001F3F7\uFE0F", 1}, +{"\u2709\uFE0F", 1}, +{"\U0001F5F3\uFE0F", 1}, +{"\u270F\uFE0F", 1}, +{"\u2712\uFE0F", 1}, +{"\U0001F58B\uFE0F", 1}, +{"\U0001F58A\uFE0F", 1}, +{"\U0001F58C\uFE0F", 1}, +{"\U0001F58D\uFE0F", 1}, +{"\U0001F5C2\uFE0F", 1}, +{"\U0001F5D2\uFE0F", 1}, +{"\U0001F5D3\uFE0F", 1}, +{"\U0001F587\uFE0F", 1}, +{"\u2702\uFE0F", 1}, +{"\U0001F5C3\uFE0F", 1}, +{"\U0001F5C4\uFE0F", 1}, +{"\U0001F5D1\uFE0F", 1}, +{"\U0001F5DD\uFE0F", 1}, +{"\u26CF\uFE0F", 1}, +{"\u2692\uFE0F", 1}, +{"\U0001F6E0\uFE0F", 1}, +{"\U0001F5E1\uFE0F", 1}, +{"\u2694\uFE0F", 1}, +{"\U0001F6E1\uFE0F", 1}, +{"\u2699\uFE0F", 1}, +{"\U0001F5DC\uFE0F", 1}, +{"\u2696\uFE0F", 1}, +{"\u26D3\uFE0F\u200D\U0001F4A5", 1}, +{"\u26D3\u200D\U0001F4A5", 1}, +{"\u26D3\uFE0F", 1}, +{"\u2697\uFE0F", 1}, +{"\U0001F6CF\uFE0F", 1}, +{"\U0001F6CB\uFE0F", 1}, +{"\u26B0\uFE0F", 1}, +{"\u26B1\uFE0F", 1}, +{"\u26A0\uFE0F", 1}, +{"\u2622\uFE0F", 1}, +{"\u2623\uFE0F", 1}, +{"\u2B06\uFE0F", 1}, +{"\u2197\uFE0F", 1}, +{"\u27A1\uFE0F", 1}, +{"\u2198\uFE0F", 1}, +{"\u2B07\uFE0F", 1}, +{"\u2199\uFE0F", 1}, +{"\u2B05\uFE0F", 1}, +{"\u2196\uFE0F", 1}, +{"\u2195\uFE0F", 1}, +{"\u2194\uFE0F", 1}, +{"\u21A9\uFE0F", 1}, +{"\u21AA\uFE0F", 1}, +{"\u2934\uFE0F", 1}, +{"\u2935\uFE0F", 1}, +{"\u269B\uFE0F", 1}, +{"\U0001F549\uFE0F", 1}, +{"\u2721\uFE0F", 1}, +{"\u2638\uFE0F", 1}, +{"\u262F\uFE0F", 1}, +{"\u271D\uFE0F", 1}, +{"\u2626\uFE0F", 1}, +{"\u262A\uFE0F", 1}, +{"\u262E\uFE0F", 1}, +{"\u25B6\uFE0F", 1}, +{"\u23ED\uFE0F", 1}, +{"\u23EF\uFE0F", 1}, +{"\u25C0\uFE0F", 1}, +{"\u23EE\uFE0F", 1}, +{"\u23F8\uFE0F", 1}, +{"\u23F9\uFE0F", 1}, +{"\u23FA\uFE0F", 1}, +{"\u23CF\uFE0F", 1}, +{"\u2640\uFE0F", 1}, +{"\u2642\uFE0F", 1}, +{"\u26A7\uFE0F", 1}, +{"\u2716\uFE0F", 1}, +{"\u267E\uFE0F", 1}, +{"\u203C\uFE0F", 1}, +{"\u2049\uFE0F", 1}, +{"\u3030\uFE0F", 1}, +{"\u2695\uFE0F", 1}, +{"\u267B\uFE0F", 1}, +{"\u269C\uFE0F", 1}, +{"\u2611\uFE0F", 1}, +{"\u2714\uFE0F", 1}, +{"\u303D\uFE0F", 1}, +{"\u2733\uFE0F", 1}, +{"\u2734\uFE0F", 1}, +{"\u2747\uFE0F", 1}, +{"\u00A9\uFE0F", 1}, +{"\u00AE\uFE0F", 1}, +{"\u2122\uFE0F", 1}, +{"\u0023\uFE0F\u20E3", 1}, +{"\u0023\u20E3", 1}, +{"\u002A\uFE0F\u20E3", 1}, +{"\u002A\u20E3", 1}, +{"\u0030\uFE0F\u20E3", 1}, +{"\u0030\u20E3", 1}, +{"\u0031\uFE0F\u20E3", 1}, +{"\u0031\u20E3", 1}, +{"\u0032\uFE0F\u20E3", 1}, +{"\u0032\u20E3", 1}, +{"\u0033\uFE0F\u20E3", 1}, +{"\u0033\u20E3", 1}, +{"\u0034\uFE0F\u20E3", 1}, +{"\u0034\u20E3", 1}, +{"\u0035\uFE0F\u20E3", 1}, +{"\u0035\u20E3", 1}, +{"\u0036\uFE0F\u20E3", 1}, +{"\u0036\u20E3", 1}, +{"\u0037\uFE0F\u20E3", 1}, +{"\u0037\u20E3", 1}, +{"\u0038\uFE0F\u20E3", 1}, +{"\u0038\u20E3", 1}, +{"\u0039\uFE0F\u20E3", 1}, +{"\u0039\u20E3", 1}, +{"\U0001F170\uFE0F", 1}, +{"\U0001F171\uFE0F", 1}, +{"\u2139\uFE0F", 1}, +{"\u24C2\uFE0F", 1}, +{"\U0001F17E\uFE0F", 1}, +{"\U0001F17F\uFE0F", 1}, +{"\U0001F202\uFE0F", 1}, +{"\U0001F237\uFE0F", 1}, +{"\u3297\uFE0F", 1}, +{"\u3299\uFE0F", 1}, +{"\u25FC\uFE0F", 1}, +{"\u25FB\uFE0F", 1}, +{"\u25AA\uFE0F", 1}, +{"\u25AB\uFE0F", 1}, +{"\U0001F3F3\uFE0F", 1}, +{"\U0001F3F3\uFE0F\u200D\U0001F308", 1}, +{"\U0001F3F3\u200D\U0001F308", 1}, +{"\U0001F3F3\uFE0F\u200D\u26A7\uFE0F", 1}, +{"\U0001F3F3\u200D\u26A7\uFE0F", 1}, +{"\U0001F3F3\uFE0F\u200D\u26A7", 1}, +{"\U0001F3F3\u200D\u26A7", 1}, +{"\U0001F3F4\u200D\u2620\uFE0F", 1}, +{"\U0001F3F4\u200D\u2620", 1}, +{"\U0001F1E6\U0001F1E8", 1}, +{"\U0001F1E6\U0001F1E9", 1}, +{"\U0001F1E6\U0001F1EA", 1}, +{"\U0001F1E6\U0001F1EB", 1}, +{"\U0001F1E6\U0001F1EC", 1}, +{"\U0001F1E6\U0001F1EE", 1}, +{"\U0001F1E6\U0001F1F1", 1}, +{"\U0001F1E6\U0001F1F2", 1}, +{"\U0001F1E6\U0001F1F4", 1}, +{"\U0001F1E6\U0001F1F6", 1}, +{"\U0001F1E6\U0001F1F7", 1}, +{"\U0001F1E6\U0001F1F8", 1}, +{"\U0001F1E6\U0001F1F9", 1}, +{"\U0001F1E6\U0001F1FA", 1}, +{"\U0001F1E6\U0001F1FC", 1}, +{"\U0001F1E6\U0001F1FD", 1}, +{"\U0001F1E6\U0001F1FF", 1}, +{"\U0001F1E7\U0001F1E6", 1}, +{"\U0001F1E7\U0001F1E7", 1}, +{"\U0001F1E7\U0001F1E9", 1}, +{"\U0001F1E7\U0001F1EA", 1}, +{"\U0001F1E7\U0001F1EB", 1}, +{"\U0001F1E7\U0001F1EC", 1}, +{"\U0001F1E7\U0001F1ED", 1}, +{"\U0001F1E7\U0001F1EE", 1}, +{"\U0001F1E7\U0001F1EF", 1}, +{"\U0001F1E7\U0001F1F1", 1}, +{"\U0001F1E7\U0001F1F2", 1}, +{"\U0001F1E7\U0001F1F3", 1}, +{"\U0001F1E7\U0001F1F4", 1}, +{"\U0001F1E7\U0001F1F6", 1}, +{"\U0001F1E7\U0001F1F7", 1}, +{"\U0001F1E7\U0001F1F8", 1}, +{"\U0001F1E7\U0001F1F9", 1}, +{"\U0001F1E7\U0001F1FB", 1}, +{"\U0001F1E7\U0001F1FC", 1}, +{"\U0001F1E7\U0001F1FE", 1}, +{"\U0001F1E7\U0001F1FF", 1}, +{"\U0001F1E8\U0001F1E6", 1}, +{"\U0001F1E8\U0001F1E8", 1}, +{"\U0001F1E8\U0001F1E9", 1}, +{"\U0001F1E8\U0001F1EB", 1}, +{"\U0001F1E8\U0001F1EC", 1}, +{"\U0001F1E8\U0001F1ED", 1}, +{"\U0001F1E8\U0001F1EE", 1}, +{"\U0001F1E8\U0001F1F0", 1}, +{"\U0001F1E8\U0001F1F1", 1}, +{"\U0001F1E8\U0001F1F2", 1}, +{"\U0001F1E8\U0001F1F3", 1}, +{"\U0001F1E8\U0001F1F4", 1}, +{"\U0001F1E8\U0001F1F5", 1}, +{"\U0001F1E8\U0001F1F7", 1}, +{"\U0001F1E8\U0001F1FA", 1}, +{"\U0001F1E8\U0001F1FB", 1}, +{"\U0001F1E8\U0001F1FC", 1}, +{"\U0001F1E8\U0001F1FD", 1}, +{"\U0001F1E8\U0001F1FE", 1}, +{"\U0001F1E8\U0001F1FF", 1}, +{"\U0001F1E9\U0001F1EA", 1}, +{"\U0001F1E9\U0001F1EC", 1}, +{"\U0001F1E9\U0001F1EF", 1}, +{"\U0001F1E9\U0001F1F0", 1}, +{"\U0001F1E9\U0001F1F2", 1}, +{"\U0001F1E9\U0001F1F4", 1}, +{"\U0001F1E9\U0001F1FF", 1}, +{"\U0001F1EA\U0001F1E6", 1}, +{"\U0001F1EA\U0001F1E8", 1}, +{"\U0001F1EA\U0001F1EA", 1}, +{"\U0001F1EA\U0001F1EC", 1}, +{"\U0001F1EA\U0001F1ED", 1}, +{"\U0001F1EA\U0001F1F7", 1}, +{"\U0001F1EA\U0001F1F8", 1}, +{"\U0001F1EA\U0001F1F9", 1}, +{"\U0001F1EA\U0001F1FA", 1}, +{"\U0001F1EB\U0001F1EE", 1}, +{"\U0001F1EB\U0001F1EF", 1}, +{"\U0001F1EB\U0001F1F0", 1}, +{"\U0001F1EB\U0001F1F2", 1}, +{"\U0001F1EB\U0001F1F4", 1}, +{"\U0001F1EB\U0001F1F7", 1}, +{"\U0001F1EC\U0001F1E6", 1}, +{"\U0001F1EC\U0001F1E7", 1}, +{"\U0001F1EC\U0001F1E9", 1}, +{"\U0001F1EC\U0001F1EA", 1}, +{"\U0001F1EC\U0001F1EB", 1}, +{"\U0001F1EC\U0001F1EC", 1}, +{"\U0001F1EC\U0001F1ED", 1}, +{"\U0001F1EC\U0001F1EE", 1}, +{"\U0001F1EC\U0001F1F1", 1}, +{"\U0001F1EC\U0001F1F2", 1}, +{"\U0001F1EC\U0001F1F3", 1}, +{"\U0001F1EC\U0001F1F5", 1}, +{"\U0001F1EC\U0001F1F6", 1}, +{"\U0001F1EC\U0001F1F7", 1}, +{"\U0001F1EC\U0001F1F8", 1}, +{"\U0001F1EC\U0001F1F9", 1}, +{"\U0001F1EC\U0001F1FA", 1}, +{"\U0001F1EC\U0001F1FC", 1}, +{"\U0001F1EC\U0001F1FE", 1}, +{"\U0001F1ED\U0001F1F0", 1}, +{"\U0001F1ED\U0001F1F2", 1}, +{"\U0001F1ED\U0001F1F3", 1}, +{"\U0001F1ED\U0001F1F7", 1}, +{"\U0001F1ED\U0001F1F9", 1}, +{"\U0001F1ED\U0001F1FA", 1}, +{"\U0001F1EE\U0001F1E8", 1}, +{"\U0001F1EE\U0001F1E9", 1}, +{"\U0001F1EE\U0001F1EA", 1}, +{"\U0001F1EE\U0001F1F1", 1}, +{"\U0001F1EE\U0001F1F2", 1}, +{"\U0001F1EE\U0001F1F3", 1}, +{"\U0001F1EE\U0001F1F4", 1}, +{"\U0001F1EE\U0001F1F6", 1}, +{"\U0001F1EE\U0001F1F7", 1}, +{"\U0001F1EE\U0001F1F8", 1}, +{"\U0001F1EE\U0001F1F9", 1}, +{"\U0001F1EF\U0001F1EA", 1}, +{"\U0001F1EF\U0001F1F2", 1}, +{"\U0001F1EF\U0001F1F4", 1}, +{"\U0001F1EF\U0001F1F5", 1}, +{"\U0001F1F0\U0001F1EA", 1}, +{"\U0001F1F0\U0001F1EC", 1}, +{"\U0001F1F0\U0001F1ED", 1}, +{"\U0001F1F0\U0001F1EE", 1}, +{"\U0001F1F0\U0001F1F2", 1}, +{"\U0001F1F0\U0001F1F3", 1}, +{"\U0001F1F0\U0001F1F5", 1}, +{"\U0001F1F0\U0001F1F7", 1}, +{"\U0001F1F0\U0001F1FC", 1}, +{"\U0001F1F0\U0001F1FE", 1}, +{"\U0001F1F0\U0001F1FF", 1}, +{"\U0001F1F1\U0001F1E6", 1}, +{"\U0001F1F1\U0001F1E7", 1}, +{"\U0001F1F1\U0001F1E8", 1}, +{"\U0001F1F1\U0001F1EE", 1}, +{"\U0001F1F1\U0001F1F0", 1}, +{"\U0001F1F1\U0001F1F7", 1}, +{"\U0001F1F1\U0001F1F8", 1}, +{"\U0001F1F1\U0001F1F9", 1}, +{"\U0001F1F1\U0001F1FA", 1}, +{"\U0001F1F1\U0001F1FB", 1}, +{"\U0001F1F1\U0001F1FE", 1}, +{"\U0001F1F2\U0001F1E6", 1}, +{"\U0001F1F2\U0001F1E8", 1}, +{"\U0001F1F2\U0001F1E9", 1}, +{"\U0001F1F2\U0001F1EA", 1}, +{"\U0001F1F2\U0001F1EB", 1}, +{"\U0001F1F2\U0001F1EC", 1}, +{"\U0001F1F2\U0001F1ED", 1}, +{"\U0001F1F2\U0001F1F0", 1}, +{"\U0001F1F2\U0001F1F1", 1}, +{"\U0001F1F2\U0001F1F2", 1}, +{"\U0001F1F2\U0001F1F3", 1}, +{"\U0001F1F2\U0001F1F4", 1}, +{"\U0001F1F2\U0001F1F5", 1}, +{"\U0001F1F2\U0001F1F6", 1}, +{"\U0001F1F2\U0001F1F7", 1}, +{"\U0001F1F2\U0001F1F8", 1}, +{"\U0001F1F2\U0001F1F9", 1}, +{"\U0001F1F2\U0001F1FA", 1}, +{"\U0001F1F2\U0001F1FB", 1}, +{"\U0001F1F2\U0001F1FC", 1}, +{"\U0001F1F2\U0001F1FD", 1}, +{"\U0001F1F2\U0001F1FE", 1}, +{"\U0001F1F2\U0001F1FF", 1}, +{"\U0001F1F3\U0001F1E6", 1}, +{"\U0001F1F3\U0001F1E8", 1}, +{"\U0001F1F3\U0001F1EA", 1}, +{"\U0001F1F3\U0001F1EB", 1}, +{"\U0001F1F3\U0001F1EC", 1}, +{"\U0001F1F3\U0001F1EE", 1}, +{"\U0001F1F3\U0001F1F1", 1}, +{"\U0001F1F3\U0001F1F4", 1}, +{"\U0001F1F3\U0001F1F5", 1}, +{"\U0001F1F3\U0001F1F7", 1}, +{"\U0001F1F3\U0001F1FA", 1}, +{"\U0001F1F3\U0001F1FF", 1}, +{"\U0001F1F4\U0001F1F2", 1}, +{"\U0001F1F5\U0001F1E6", 1}, +{"\U0001F1F5\U0001F1EA", 1}, +{"\U0001F1F5\U0001F1EB", 1}, +{"\U0001F1F5\U0001F1EC", 1}, +{"\U0001F1F5\U0001F1ED", 1}, +{"\U0001F1F5\U0001F1F0", 1}, +{"\U0001F1F5\U0001F1F1", 1}, +{"\U0001F1F5\U0001F1F2", 1}, +{"\U0001F1F5\U0001F1F3", 1}, +{"\U0001F1F5\U0001F1F7", 1}, +{"\U0001F1F5\U0001F1F8", 1}, +{"\U0001F1F5\U0001F1F9", 1}, +{"\U0001F1F5\U0001F1FC", 1}, +{"\U0001F1F5\U0001F1FE", 1}, +{"\U0001F1F6\U0001F1E6", 1}, +{"\U0001F1F7\U0001F1EA", 1}, +{"\U0001F1F7\U0001F1F4", 1}, +{"\U0001F1F7\U0001F1F8", 1}, +{"\U0001F1F7\U0001F1FA", 1}, +{"\U0001F1F7\U0001F1FC", 1}, +{"\U0001F1F8\U0001F1E6", 1}, +{"\U0001F1F8\U0001F1E7", 1}, +{"\U0001F1F8\U0001F1E8", 1}, +{"\U0001F1F8\U0001F1E9", 1}, +{"\U0001F1F8\U0001F1EA", 1}, +{"\U0001F1F8\U0001F1EC", 1}, +{"\U0001F1F8\U0001F1ED", 1}, +{"\U0001F1F8\U0001F1EE", 1}, +{"\U0001F1F8\U0001F1EF", 1}, +{"\U0001F1F8\U0001F1F0", 1}, +{"\U0001F1F8\U0001F1F1", 1}, +{"\U0001F1F8\U0001F1F2", 1}, +{"\U0001F1F8\U0001F1F3", 1}, +{"\U0001F1F8\U0001F1F4", 1}, +{"\U0001F1F8\U0001F1F7", 1}, +{"\U0001F1F8\U0001F1F8", 1}, +{"\U0001F1F8\U0001F1F9", 1}, +{"\U0001F1F8\U0001F1FB", 1}, +{"\U0001F1F8\U0001F1FD", 1}, +{"\U0001F1F8\U0001F1FE", 1}, +{"\U0001F1F8\U0001F1FF", 1}, +{"\U0001F1F9\U0001F1E6", 1}, +{"\U0001F1F9\U0001F1E8", 1}, +{"\U0001F1F9\U0001F1E9", 1}, +{"\U0001F1F9\U0001F1EB", 1}, +{"\U0001F1F9\U0001F1EC", 1}, +{"\U0001F1F9\U0001F1ED", 1}, +{"\U0001F1F9\U0001F1EF", 1}, +{"\U0001F1F9\U0001F1F0", 1}, +{"\U0001F1F9\U0001F1F1", 1}, +{"\U0001F1F9\U0001F1F2", 1}, +{"\U0001F1F9\U0001F1F3", 1}, +{"\U0001F1F9\U0001F1F4", 1}, +{"\U0001F1F9\U0001F1F7", 1}, +{"\U0001F1F9\U0001F1F9", 1}, +{"\U0001F1F9\U0001F1FB", 1}, +{"\U0001F1F9\U0001F1FC", 1}, +{"\U0001F1F9\U0001F1FF", 1}, +{"\U0001F1FA\U0001F1E6", 1}, +{"\U0001F1FA\U0001F1EC", 1}, +{"\U0001F1FA\U0001F1F2", 1}, +{"\U0001F1FA\U0001F1F3", 1}, +{"\U0001F1FA\U0001F1F8", 1}, +{"\U0001F1FA\U0001F1FE", 1}, +{"\U0001F1FA\U0001F1FF", 1}, +{"\U0001F1FB\U0001F1E6", 1}, +{"\U0001F1FB\U0001F1E8", 1}, +{"\U0001F1FB\U0001F1EA", 1}, +{"\U0001F1FB\U0001F1EC", 1}, +{"\U0001F1FB\U0001F1EE", 1}, +{"\U0001F1FB\U0001F1F3", 1}, +{"\U0001F1FB\U0001F1FA", 1}, +{"\U0001F1FC\U0001F1EB", 1}, +{"\U0001F1FC\U0001F1F8", 1}, +{"\U0001F1FD\U0001F1F0", 1}, +{"\U0001F1FE\U0001F1EA", 1}, +{"\U0001F1FE\U0001F1F9", 1}, +{"\U0001F1FF\U0001F1E6", 1}, +{"\U0001F1FF\U0001F1F2", 1}, +{"\U0001F1FF\U0001F1FC", 1}, +{"\U0001F3F4\U000E0067\U000E0062\U000E0065\U000E006E\U000E0067\U000E007F", 1}, +{"\U0001F3F4\U000E0067\U000E0062\U000E0073\U000E0063\U000E0074\U000E007F", 1}, +{"\U0001F3F4\U000E0067\U000E0062\U000E0077\U000E006C\U000E0073\U000E007F", 1}, +} diff --git a/tests/internal/Makefile b/tests/internal/Makefile deleted file mode 100644 index c5c612cdd..000000000 --- a/tests/internal/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -ODIN=../../odin - -all: rtti_test map_test pow_test 128_test asan_test - -rtti_test: - $(ODIN) run test_rtti.odin -file -vet -strict-style -o:minimal - -map_test: - $(ODIN) run test_map.odin -file -vet -strict-style -o:minimal - -pow_test: - $(ODIN) run test_pow.odin -file -vet -strict-style -o:minimal - -128_test: - $(ODIN) run test_128.odin -file -vet -strict-style -o:minimal - -asan_test: - $(ODIN) run test_asan.odin -file -sanitize:address -debug diff --git a/tests/internal/build.bat b/tests/internal/build.bat deleted file mode 100644 index da4fe890d..000000000 --- a/tests/internal/build.bat +++ /dev/null @@ -1,8 +0,0 @@ -@echo off -set PATH_TO_ODIN==..\..\odin -rem %PATH_TO_ODIN% run test_rtti.odin -file -vet -strict-style -o:minimal || exit /b -%PATH_TO_ODIN% run test_map.odin -file -vet -strict-style -o:minimal || exit /b -rem -define:SEED=42 -%PATH_TO_ODIN% run test_pow.odin -file -vet -strict-style -o:minimal || exit /b - -%PATH_TO_ODIN% run test_128.odin -file -vet -strict-style -o:minimal || exit /b diff --git a/tests/internal/test_128.odin b/tests/internal/test_128.odin index 11ef068ed..7b7d655e8 100644 --- a/tests/internal/test_128.odin +++ b/tests/internal/test_128.odin @@ -1,41 +1,7 @@ -package test_128 +package test_internal -import "core:fmt" -import "core:os" import "core:testing" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - test_128_align(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - @test test_128_align :: proc(t: ^testing.T) { Danger_Struct :: struct { @@ -45,15 +11,15 @@ test_128_align :: proc(t: ^testing.T) { list := [?]Danger_Struct{{0, 0}, {1, 0}, {2, 0}, {3, 0}} - expect(t, list[0].x == 0, fmt.tprintf("[0].x (%v) != 0", list[0].x)) - expect(t, list[0].y == 0, fmt.tprintf("[0].y (%v) != 0", list[0].y)) + testing.expectf(t, list[0].x == 0, "[0].x (%v) != 0", list[0].x) + testing.expectf(t, list[0].y == 0, "[0].y (%v) != 0", list[0].y) - expect(t, list[1].x == 1, fmt.tprintf("[1].x (%v) != 1", list[1].x)) - expect(t, list[1].y == 0, fmt.tprintf("[1].y (%v) != 0", list[1].y)) + testing.expectf(t, list[1].x == 1, "[1].x (%v) != 1", list[1].x) + testing.expectf(t, list[1].y == 0, "[1].y (%v) != 0", list[1].y) - expect(t, list[2].x == 2, fmt.tprintf("[2].x (%v) != 2", list[2].x)) - expect(t, list[2].y == 0, fmt.tprintf("[2].y (%v) != 0", list[2].y)) + testing.expectf(t, list[2].x == 2, "[2].x (%v) != 2", list[2].x) + testing.expectf(t, list[2].y == 0, "[2].y (%v) != 0", list[2].y) - expect(t, list[3].x == 3, fmt.tprintf("[3].x (%v) != 3", list[3].x)) - expect(t, list[3].y == 0, fmt.tprintf("[3].y (%v) != 0", list[3].y)) + testing.expectf(t, list[3].x == 3, "[3].x (%v) != 3", list[3].x) + testing.expectf(t, list[3].y == 0, "[3].y (%v) != 0", list[3].y) } diff --git a/tests/internal/test_asan.odin b/tests/internal/test_asan.odin index 2384ada76..1ac599acf 100644 --- a/tests/internal/test_asan.odin +++ b/tests/internal/test_asan.odin @@ -1,42 +1,7 @@ // Intended to contain code that would trigger asan easily if the abi was set up badly. -package test_asan +package test_internal -import "core:fmt" import "core:testing" -import "core:os" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - test_12_bytes(&t) - test_12_bytes_two(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} @(test) test_12_bytes :: proc(t: ^testing.T) { @@ -45,9 +10,9 @@ test_12_bytes :: proc(t: ^testing.T) { } a, b, ok := internal() - expect(t, a == max(f32), fmt.tprintf("a (%v) != max(f32)", a)) - expect(t, b == 0, fmt.tprintf("b (%v) != 0", b)) - expect(t, ok, fmt.tprintf("ok (%v) != true", ok)) + testing.expectf(t, a == max(f32), "a (%v) != max(f32)", a) + testing.expectf(t, b == 0, "b (%v) != 0", b) + testing.expectf(t, ok, "ok (%v) != true", ok) } @(test) @@ -57,6 +22,6 @@ test_12_bytes_two :: proc(t: ^testing.T) { } a, b := internal() - expect(t, a == 100., fmt.tprintf("a (%v) != 100.", a)) - expect(t, b == max(int), fmt.tprintf("b (%v) != max(int)", b)) + testing.expectf(t, a == 100., "a (%v) != 100.", a) + testing.expectf(t, b == max(int), "b (%v) != max(int)", b) } diff --git a/tests/internal/test_map.odin b/tests/internal/test_map.odin index 7d1dbf470..9bd5d34ea 100644 --- a/tests/internal/test_map.odin +++ b/tests/internal/test_map.odin @@ -1,29 +1,25 @@ -package test_internal_map +package test_internal -import "core:fmt" +import "core:log" import "base:intrinsics" import "core:math/rand" -import "core:mem" -import "core:os" import "core:testing" -seed: u64 - ENTRY_COUNTS := []int{11, 101, 1_001, 10_001, 100_001, 1_000_001} @test map_insert_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[map_insert_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]i64 defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + rand.reset(t.seed + seed_incr) for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k])) + testing.expectf(t, false, "Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k]) } } seed_incr += 1 @@ -65,15 +61,16 @@ map_insert_random_key_value :: proc(t: ^testing.T) { map_update_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[map_update_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]i64 defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + rand.reset(t.seed + seed_incr) + for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k])) + testing.expectf(t, false, "Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k]) } } seed_incr += 1 @@ -127,15 +125,16 @@ map_update_random_key_value :: proc(t: ^testing.T) { map_delete_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[map_delete_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]i64 defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + rand.reset(t.seed + seed_incr) + for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected key present. Expected m[%v] to have been deleted, got %v", k, m[k])) + testing.expectf(t, false, "Unexpected key present. Expected m[%v] to have been deleted, got %v", k, m[k]) } } else { if k not_in m { num_fails += 1 if num_fails > 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Expected key not present. Expected m[%v] = %v", k, v)) + testing.expectf(t, false, "Expected key not present. Expected m[%v] = %v", k, v) } else if m[k] != v { num_fails += 1 if num_fails > 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k])) + testing.expectf(t, false, "Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k]) } } } @@ -205,14 +205,15 @@ map_delete_random_key_value :: proc(t: ^testing.T) { set_insert_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[set_insert_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]struct{} defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + rand.reset(t.seed + seed_incr) + for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] to exist", k)) + testing.expectf(t, false, "Unexpected value. Expected m[%v] to exist", k) } } seed_incr += 1 @@ -252,14 +253,15 @@ set_insert_random_key_value :: proc(t: ^testing.T) { set_delete_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[set_delete_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]struct{} defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + rand.reset(t.seed + seed_incr) + for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected key present. Expected m[%v] to have been deleted", k)) + testing.expectf(t, false, "Unexpected key present. Expected m[%v] to have been deleted", k) } } else { if k not_in m { num_fails += 1 if num_fails > 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Expected key not present. Expected m[%v] to exist", k)) + testing.expectf(t, false, "Expected key not present. Expected m[%v] to exist", k) } } } seed_incr += 1 } } - -// -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- - -main :: proc() { - t := testing.T{} - - // Allow tests to be repeatable - SEED :: #config(SEED, -1) - when SEED > 0 { - seed = u64(SEED) - } else { - seed = u64(intrinsics.read_cycle_counter()) - } - fmt.println("Initialized seed to", seed) - - mem_track_test(&t, map_insert_random_key_value) - mem_track_test(&t, map_update_random_key_value) - mem_track_test(&t, map_delete_random_key_value) - - mem_track_test(&t, set_insert_random_key_value) - mem_track_test(&t, set_delete_random_key_value) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -mem_track_test :: proc(t: ^testing.T, test: proc(t: ^testing.T)) { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - test(t) - - expect(t, len(track.allocation_map) == 0, "Expected no leaks.") - expect(t, len(track.bad_free_array) == 0, "Expected no leaks.") - - for _, leak in track.allocation_map { - fmt.printf("%v leaked %v bytes\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) - } -} - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} diff --git a/tests/internal/test_pow.odin b/tests/internal/test_pow.odin index 70b81258d..d1939ace5 100644 --- a/tests/internal/test_pow.odin +++ b/tests/internal/test_pow.odin @@ -1,8 +1,7 @@ -package test_internal_math_pow +package test_internal -import "core:fmt" +@(require) import "core:log" import "core:math" -import "core:os" import "core:testing" @test @@ -19,14 +18,14 @@ pow_test :: proc(t: ^testing.T) { // pow2_f64 returns the same float on all platforms because it isn't this stupid _v1 = 0h00000000_00000000 } - expect(t, _v1 == _v2, fmt.tprintf("Expected math.pow2_f64(%d) == math.pow(2, %d) (= %16x), got %16x", exp, exp, _v1, _v2)) + testing.expectf(t, _v1 == _v2, "Expected math.pow2_f64(%d) == math.pow(2, %d) (= %16x), got %16x", exp, exp, _v1, _v2) } { v1 := math.pow(2, f32(exp)) v2 := math.pow2_f32(exp) _v1 := transmute(u32)v1 _v2 := transmute(u32)v2 - expect(t, _v1 == _v2, fmt.tprintf("Expected math.pow2_f32(%d) == math.pow(2, %d) (= %08x), got %08x", exp, exp, _v1, _v2)) + testing.expectf(t, _v1 == _v2, "Expected math.pow2_f32(%d) == math.pow(2, %d) (= %08x), got %08x", exp, exp, _v1, _v2) } { v1 := math.pow(2, f16(exp)) @@ -36,46 +35,11 @@ pow_test :: proc(t: ^testing.T) { when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 { if exp == -25 { - testing.logf(t, "skipping known test failure on darwin+arm64, Expected math.pow2_f16(-25) == math.pow(2, -25) (= 0000), got 0001") + log.info("skipping known test failure on darwin+arm64, Expected math.pow2_f16(-25) == math.pow(2, -25) (= 0000), got 0001") _v2 = 0 } } - - expect(t, _v1 == _v2, fmt.tprintf("Expected math.pow2_f16(%d) == math.pow(2, %d) (= %04x), got %04x", exp, exp, _v1, _v2)) + testing.expectf(t, _v1 == _v2, "Expected math.pow2_f16(%d) == math.pow(2, %d) (= %04x), got %04x", exp, exp, _v1, _v2) } } } - -// -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- - -main :: proc() { - t := testing.T{} - - pow_test(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} diff --git a/tests/internal/test_rtti.odin b/tests/internal/test_rtti.odin index 12f64462b..e72c107b2 100644 --- a/tests/internal/test_rtti.odin +++ b/tests/internal/test_rtti.odin @@ -1,11 +1,8 @@ -package test_internal_rtti +package test_internal import "core:fmt" -import "core:mem" -import "core:os" import "core:testing" - Buggy_Struct :: struct { a: int, b: bool, @@ -28,74 +25,22 @@ rtti_test :: proc(t: ^testing.T) { for v, i in g_b { checksum += (i+1) * int(v) } - expect(t, checksum == 0, fmt.tprintf("Expected g_b to be zero-initialized, got %v", g_b)) + testing.expectf(t, checksum == 0, "Expected g_b to be zero-initialized, got %v", g_b) } { checksum := 0 for v, i in l_b { checksum += (i+1) * int(v) } - expect(t, checksum == 0, fmt.tprintf("Expected l_b to be zero-initialized, got %v", l_b)) + testing.expectf(t, checksum == 0, "Expected l_b to be zero-initialized, got %v", l_b) } - expect(t, size_of(Buggy_Struct) == 40, fmt.tprintf("Expected size_of(Buggy_Struct) == 40, got %v", size_of(Buggy_Struct))) - expect(t, size_of(g_buggy) == 40, fmt.tprintf("Expected size_of(g_buggy) == 40, got %v", size_of(g_buggy))) - expect(t, size_of(l_buggy) == 40, fmt.tprintf("Expected size_of(l_buggy) == 40, got %v", size_of(l_buggy))) + testing.expectf(t, size_of(Buggy_Struct) == 40, "Expected size_of(Buggy_Struct) == 40, got %v", size_of(Buggy_Struct)) + testing.expectf(t, size_of(g_buggy) == 40, "Expected size_of(g_buggy) == 40, got %v", size_of(g_buggy)) + testing.expectf(t, size_of(l_buggy) == 40, "Expected size_of(l_buggy) == 40, got %v", size_of(l_buggy)) g_s := fmt.tprintf("%s", g_buggy) l_s := fmt.tprintf("%s", l_buggy) - expect(t, g_s == EXPECTED_REPR, fmt.tprintf("Expected fmt.tprintf(\"%%s\", g_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, g_s)) - expect(t, l_s == EXPECTED_REPR, fmt.tprintf("Expected fmt.tprintf(\"%%s\", l_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, l_s)) + testing.expectf(t, g_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", g_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, g_s) + testing.expectf(t, l_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", l_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, l_s) } - -// -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- - -main :: proc() { - t := testing.T{} - - mem_track_test(&t, rtti_test) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -mem_track_test :: proc(t: ^testing.T, test: proc(t: ^testing.T)) { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - test(t) - - expect(t, len(track.allocation_map) == 0, "Expected no leaks.") - expect(t, len(track.bad_free_array) == 0, "Expected no leaks.") - - for _, leak in track.allocation_map { - fmt.printf("%v leaked %v bytes\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) - } -} - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} \ No newline at end of file diff --git a/tests/internal/test_string_compare.odin b/tests/internal/test_string_compare.odin new file mode 100644 index 000000000..32a93ddf5 --- /dev/null +++ b/tests/internal/test_string_compare.odin @@ -0,0 +1,57 @@ +package test_internal + +import "core:testing" + +Op :: enum { Eq, Lt, Gt } + +Test :: struct { + a: cstring, + b: cstring, + res: [Op]bool, +} + +CASES := []Test{ + {"hellope", "hellope", {.Eq=true, .Lt=false, .Gt=false}}, + {"Hellope", "hellope", {.Eq=false, .Lt=true, .Gt=false}}, // H < h + {"Hell", "Hellope", {.Eq=false, .Lt=true, .Gt=false}}, + {"Hellope!", "Hellope", {.Eq=false, .Lt=false, .Gt=true }}, + {"Hellopf", "Hellope", {.Eq=false, .Lt=false, .Gt=true }}, +} + +@test +string_compare :: proc(t: ^testing.T) { + for v in CASES { + s_a := string(v.a) + s_b := string(v.b) + + for res, op in v.res { + switch op { + case .Eq: + testing.expectf(t, (v.a == v.b) == res, "Expected cstring(\"%v\") == cstring(\"%v\") to be %v", v.a, v.b, res) + testing.expectf(t, (s_a == s_b) == res, "Expected string(\"%v\") == string(\"%v\") to be %v", v.a, v.b, res) + + // If a == b then a != b + testing.expectf(t, (v.a != v.b) == !res, "Expected cstring(\"%v\") != cstring(\"%v\") to be %v", v.a, v.b, !res) + testing.expectf(t, (s_a != s_b) == !res, "Expected string(\"%v\") != string(\"%v\") to be %v", v.a, v.b, !res) + + case .Lt: + testing.expectf(t, (v.a < v.b) == res, "Expected cstring(\"%v\") < cstring(\"%v\") to be %v", v.a, v.b, res) + testing.expectf(t, (s_a < s_b) == res, "Expected string(\"%v\") < string(\"%v\") to be %v", v.a, v.b, res) + + // .Lt | .Eq == .LtEq + lteq := v.res[.Eq] | res + testing.expectf(t, (v.a <= v.b) == lteq, "Expected cstring(\"%v\") <= cstring(\"%v\") to be %v", v.a, v.b, lteq) + testing.expectf(t, (s_a <= s_b) == lteq, "Expected string(\"%v\") <= string(\"%v\") to be %v", v.a, v.b, lteq) + + case .Gt: + testing.expectf(t, (v.a > v.b) == res, "Expected cstring(\"%v\") > cstring(\"%v\") to be %v", v.a, v.b, res) + testing.expectf(t, (s_a > s_b) == res, "Expected string(\"%v\") > string(\"%v\") to be %v", v.a, v.b, res) + + // .Gt | .Eq == .GtEq + gteq := v.res[.Eq] | res + testing.expectf(t, (v.a >= v.b) == gteq, "Expected cstring(\"%v\") >= cstring(\"%v\") to be %v", v.a, v.b, gteq) + testing.expectf(t, (s_a >= s_b) == gteq, "Expected string(\"%v\") >= string(\"%v\") to be %v", v.a, v.b, gteq) + } + } + } +} diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 41c52c02f..299e08791 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -3,19 +3,18 @@ if not exist "build\" mkdir build pushd build -set COMMON=-collection:tests=..\.. +set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style @echo on -..\..\..\odin test ..\test_issue_829.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_1592.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2056.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2087.odin %COMMON% -file || exit /b -..\..\..\odin build ..\test_issue_2113.odin %COMMON% -file -debug || exit /b -..\..\..\odin test ..\test_issue_2466.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2615.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2637.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2666.odin %COMMON% -file || exit /b +..\..\..\odin test ..\test_issue_829.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_1592.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_2056.odin %COMMON% || exit /b +..\..\..\odin build ..\test_issue_2113.odin %COMMON% -debug || exit /b +..\..\..\odin test ..\test_issue_2466.odin %COMMON% || exit /b +..\..\..\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 @echo off diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 6d53388a7..8b4c1e7f2 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -1,28 +1,26 @@ -#!/bin/bash +#!/usr/bin/env bash set -eu mkdir -p build pushd build ODIN=../../../odin -COMMON="-collection:tests=../.." - -NO_NIL_ERR="Error: " +COMMON="-define:ODIN_TEST_FANCY=false -file -vet -strict-style" set -x -$ODIN test ../test_issue_829.odin $COMMON -file -$ODIN test ../test_issue_1592.odin $COMMON -file -$ODIN test ../test_issue_2056.odin $COMMON -file -$ODIN test ../test_issue_2087.odin $COMMON -file -$ODIN build ../test_issue_2113.odin $COMMON -file -debug -$ODIN test ../test_issue_2466.odin $COMMON -file -$ODIN test ../test_issue_2615.odin $COMMON -file -$ODIN test ../test_issue_2637.odin $COMMON -file -$ODIN test ../test_issue_2666.odin $COMMON -file -if [[ $($ODIN build ../test_issue_2395.odin $COMMON -file 2>&1 >/dev/null | grep -c "$NO_NIL_ERR") -eq 2 ]] ; then +$ODIN test ../test_issue_829.odin $COMMON +$ODIN test ../test_issue_1592.odin $COMMON +$ODIN test ../test_issue_2056.odin $COMMON +$ODIN build ../test_issue_2113.odin $COMMON -debug +$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 +if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]] ; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" + exit 1 fi set +x diff --git a/tests/issues/test_issue_1592.odin b/tests/issues/test_issue_1592.odin index 800314a93..79eff33df 100644 --- a/tests/issues/test_issue_1592.odin +++ b/tests/issues/test_issue_1592.odin @@ -1,7 +1,6 @@ // Tests issue #1592 https://github.com/odin-lang/Odin/issues/1592 package test_issues -import "core:fmt" import "core:testing" /* Original issue #1592 example */ @@ -31,428 +30,428 @@ true_result :: proc() -> bool { @test test_simple_const_false :: proc(t: ^testing.T) { if CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if (CONSTANT_FALSE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if (!CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !!CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE == true { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE == false { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_FALSE == true) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_FALSE == false) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } } @test test_simple_const_true :: proc(t: ^testing.T) { if CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if (CONSTANT_TRUE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if (!CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if (!CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !!CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE == true { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE == false { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE == true) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE == false) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_simple_proc_false :: proc(t: ^testing.T) { if false_result() { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !false_result() { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_simple_proc_true :: proc(t: ^testing.T) { if true_result() { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !true_result() { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } } @test test_const_false_const_false :: proc(t: ^testing.T) { if CONSTANT_FALSE || CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_FALSE || CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_FALSE && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE || !CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_FALSE && !CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_FALSE || CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_FALSE && CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_const_false_const_true :: proc(t: ^testing.T) { if CONSTANT_FALSE || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_FALSE && CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_FALSE || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_FALSE && CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_FALSE || !CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE && !CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_FALSE || CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_FALSE && CONSTANT_TRUE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_const_true_const_false :: proc(t: ^testing.T) { if CONSTANT_TRUE || CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_TRUE || CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_TRUE && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_TRUE || !CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE && !CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_TRUE || CONSTANT_FALSE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE && CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_const_true_const_true :: proc(t: ^testing.T) { if CONSTANT_TRUE || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE && CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_TRUE || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_TRUE && CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_TRUE || !CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE && !CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE || CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE && CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } } @test test_proc_false_const_false :: proc(t: ^testing.T) { if false_result() || CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if false_result() && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(false_result() || CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(false_result() && CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_proc_false_const_true :: proc(t: ^testing.T) { if false_result() || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if false_result() && CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(false_result() || CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(false_result() && CONSTANT_TRUE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_proc_true_const_false :: proc(t: ^testing.T) { if true_result() || CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if true_result() && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(true_result() || CONSTANT_FALSE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(true_result() && CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_proc_true_const_true :: proc(t: ^testing.T) { if true_result() || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if true_result() && CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(true_result() || CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(true_result() && CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } } diff --git a/tests/issues/test_issue_2056.odin b/tests/issues/test_issue_2056.odin index 4869b557e..06bc11fba 100644 --- a/tests/issues/test_issue_2056.odin +++ b/tests/issues/test_issue_2056.odin @@ -1,7 +1,6 @@ // Tests issue #2056 https://github.com/odin-lang/Odin/issues/2056 package test_issues -import "core:fmt" import "core:testing" @test @@ -12,9 +11,9 @@ test_scalar_matrix_conversion :: proc(t: ^testing.T) { for i in 0..<4 { for j in 0..<4 { if i == j { - testing.expect(t, m[i,j] == 1, fmt.tprintf("expected 1 at m[%d,%d], found %f\n", i, j, m[i,j])) + testing.expectf(t, m[i,j] == 1, "expected 1 at m[%d,%d], found %f\n", i, j, m[i,j]) } else { - testing.expect(t, m[i,j] == 0, fmt.tprintf("expected 0 at m[%d,%d], found %f\n", i, j, m[i,j])) + testing.expectf(t, m[i,j] == 0, "expected 0 at m[%d,%d], found %f\n", i, j, m[i,j]) } } } diff --git a/tests/issues/test_issue_2087.odin b/tests/issues/test_issue_2087.odin deleted file mode 100644 index 26b6d487d..000000000 --- a/tests/issues/test_issue_2087.odin +++ /dev/null @@ -1,62 +0,0 @@ -// Tests issue #2087 https://github.com/odin-lang/Odin/issues/2087 -package test_issues - -import "core:math" -import "core:strconv" -import "core:testing" - -@(test) -test_parse_float :: proc(t: ^testing.T) { - { - f, ok := strconv.parse_f64("1.2") - testing.expect(t, ok && f == 1.2, "expected f64(1.2), fully consumed") - f, ok = strconv.parse_f64("1.2a") - testing.expect(t, !ok && f == 1.2, "expected f64(1.2), partially consumed") - f, ok = strconv.parse_f64("+") - testing.expect(t, !ok && f == 0.0, "expected f64(0.0), with ok=false") - f, ok = strconv.parse_f64("-") - testing.expect(t, !ok && f == 0.0, "expected f64(0.0), with ok=false") - - - f, ok = strconv.parse_f64("inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), fully consumed") - f, ok = strconv.parse_f64("+inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), fully consumed") - f, ok = strconv.parse_f64("-inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f64(-inf), fully consumed") - f, ok = strconv.parse_f64("inFinity") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), partially consumed") - f, ok = strconv.parse_f64("+InFinity") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), partially consumed") - f, ok = strconv.parse_f64("-InfiniTy") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f64(-inf), partially consumed") - f, ok = strconv.parse_f64("nan") - testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f64(nan), fully consumed") - f, ok = strconv.parse_f64("nAN") - testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f64(nan), fully consumed") - } - { - f, ok := strconv.parse_f32("1.2") - testing.expect(t, ok && f == 1.2, "expected f32(1.2), fully consumed") - - f, ok = strconv.parse_f32("1.2a") - testing.expect(t, !ok && f == 1.2, "expected f32(1.2), partially consumed") - - f, ok = strconv.parse_f32("inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), fully consumed") - f, ok = strconv.parse_f32("+inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), fully consumed") - f, ok = strconv.parse_f32("-inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f32(-inf), fully consumed") - f, ok = strconv.parse_f32("inFinity") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), partially consumed") - f, ok = strconv.parse_f32("+InFinity") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), partially consumed") - f, ok = strconv.parse_f32("-InfiniTy") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f32(-inf), partially consumed") - f, ok = strconv.parse_f32("nan") - testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f32(nan), fully consumed") - f, ok = strconv.parse_f32("nAN") - testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f32(nan), fully consumed") - } -} \ No newline at end of file diff --git a/tests/issues/test_issue_2395.odin b/tests/issues/test_issue_2395.odin index 48e1ee516..bbbcb3aea 100644 --- a/tests/issues/test_issue_2395.odin +++ b/tests/issues/test_issue_2395.odin @@ -5,8 +5,6 @@ // exactly 2 errors from the invalid unions package test_issues -import "core:testing" - ValidUnion :: union($T: typeid) #no_nil { T, f32, diff --git a/tests/issues/test_issue_2466.odin b/tests/issues/test_issue_2466.odin index 4810cfea9..f5987903a 100644 --- a/tests/issues/test_issue_2466.odin +++ b/tests/issues/test_issue_2466.odin @@ -1,7 +1,6 @@ // Tests issue #2466 https://github.com/odin-lang/Odin/issues/2466 package test_issues -import "core:fmt" import "core:testing" Bug :: struct { @@ -16,7 +15,7 @@ test_compound_literal_local_reuse :: proc(t: ^testing.T) { val = v, arr = {42}, } - testing.expect(t, bug.val == 123, fmt.tprintf("expected 123, found %d", bug.val)) - testing.expect(t, bug.arr[0] == 42, fmt.tprintf("expected 42, found %d", bug.arr[0])) + testing.expectf(t, bug.val == 123, "expected 123, found %d", bug.val) + testing.expectf(t, bug.arr[0] == 42, "expected 42, found %d", bug.arr[0]) } diff --git a/tests/issues/test_issue_829.odin b/tests/issues/test_issue_829.odin index 273b3b3b5..229d8e9b4 100644 --- a/tests/issues/test_issue_829.odin +++ b/tests/issues/test_issue_829.odin @@ -1,7 +1,6 @@ // Tests issue #829 https://github.com/odin-lang/Odin/issues/829 package test_issues -import "core:fmt" import "core:testing" /* Original issue #829 example */ @@ -13,6 +12,6 @@ env : map[string]proc(a, b : int) -> int = { @(test) test_orig_ret :: proc(t: ^testing.T) { - r := fmt.tprint(env["+"](1, 2)) - testing.expect(t, r == "3", fmt.tprintf("%s: \"%s\" != \"3\"\n", #procedure, r)) -} + r := env["+"](1, 2) + testing.expectf(t, r == 3, "%q != 3", r) +} \ No newline at end of file diff --git a/tests/vendor/Makefile b/tests/vendor/Makefile deleted file mode 100644 index 7d6b84978..000000000 --- a/tests/vendor/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -ODIN=../../odin -ODINFLAGS= - -OS=$(shell uname) - -ifeq ($(OS), OpenBSD) - ODINFLAGS:=$(ODINFLAGS) -extra-linker-flags:-L/usr/local/lib -endif - -all: diff --git a/tests/vendor/all.odin b/tests/vendor/all.odin new file mode 100644 index 000000000..1ce56e786 --- /dev/null +++ b/tests/vendor/all.odin @@ -0,0 +1,3 @@ +package tests_vendor + +@(require) import "glfw" diff --git a/tests/vendor/build.bat b/tests/vendor/build.bat deleted file mode 100644 index 693d344f4..000000000 --- a/tests/vendor/build.bat +++ /dev/null @@ -1,8 +0,0 @@ -@echo off -set COMMON=-show-timings -no-bounds-check -vet -strict-style -set PATH_TO_ODIN==..\..\odin - -echo --- -echo Running vendor:glfw tests -echo --- -%PATH_TO_ODIN% run glfw %COMMON% -out:vendor_glfw.exe || exit /b \ No newline at end of file diff --git a/tests/vendor/glfw/test_vendor_glfw.odin b/tests/vendor/glfw/test_vendor_glfw.odin index ce55ad7ef..8a7fb0d0a 100644 --- a/tests/vendor/glfw/test_vendor_glfw.odin +++ b/tests/vendor/glfw/test_vendor_glfw.odin @@ -1,49 +1,22 @@ +//+build darwin, windows package test_vendor_glfw import "core:testing" -import "core:fmt" import "vendor:glfw" -import "core:os" GLFW_MAJOR :: 3 GLFW_MINOR :: 4 GLFW_PATCH :: 0 -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - fmt.printf("[%v] ", loc) - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.println(message) - return - } - fmt.println(" PASS") - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_glfw(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - @(test) test_glfw :: proc(t: ^testing.T) { major, minor, patch := glfw.GetVersion() - expect(t, major == GLFW_MAJOR && minor == GLFW_MINOR, fmt.tprintf("Expected GLFW.GetVersion: %v.%v.%v, got %v.%v.%v instead", GLFW_MAJOR, GLFW_MINOR, GLFW_PATCH, major, minor, patch)) + testing.expectf( + t, + major == GLFW_MAJOR && \ + minor == GLFW_MINOR, + "Expected GLFW.GetVersion: %v.%v.%v, got %v.%v.%v instead", + GLFW_MAJOR, GLFW_MINOR, GLFW_PATCH, + major, minor, patch, + ) } diff --git a/vendor/ENet/unix.odin b/vendor/ENet/unix.odin index 05ce41e05..0e3399aeb 100644 --- a/vendor/ENet/unix.odin +++ b/vendor/ENet/unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd +//+build linux, darwin, freebsd, openbsd, netbsd package ENet // When we implement the appropriate bindings for Unix, the section separated @@ -23,7 +23,7 @@ import "core:c" } @(private="file") FD_CLR :: #force_inline proc(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)))) + 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 { diff --git a/vendor/ENet/win32.odin b/vendor/ENet/win32.odin index 1df35ee45..d05cf5d99 100644 --- a/vendor/ENet/win32.odin +++ b/vendor/ENet/win32.odin @@ -39,7 +39,9 @@ foreign WinSock2 { return } } - if s.fd_count >= FD_SETSIZE do return + if s.fd_count >= FD_SETSIZE { + return + } s.fd_array[s.fd_count] = fd s.fd_count += 1 } diff --git a/vendor/OpenGL/wrappers.odin b/vendor/OpenGL/wrappers.odin index a04df6987..1eb8fc72f 100644 --- a/vendor/OpenGL/wrappers.odin +++ b/vendor/OpenGL/wrappers.odin @@ -454,20 +454,13 @@ when !GL_DEBUG { BeginQueryIndexed :: proc "c" (target: u32, index: u32, id: u32) { impl_BeginQueryIndexed(target, index, id) } EndQueryIndexed :: proc "c" (target: u32, index: u32) { impl_EndQueryIndexed(target, index) } GetQueryIndexediv :: proc "c" (target: u32, index: u32, pname: u32, params: [^]i32) { impl_GetQueryIndexediv(target, index, pname, params) } - GetTextureHandleARB :: proc "c" (texture: u32) -> u64 - { return impl_GetTextureHandleARB(texture) } - GetTextureSamplerHandleARB :: proc "c" (texture, sampler: u32) -> u64 - { return impl_GetTextureSamplerHandleARB(texture, sampler) } - GetImageHandleARB :: proc "c" (texture: u32, level: i32, layered: bool, layer: i32, format: u32) -> u64 - { return impl_GetImageHandleARB(texture, level, layered, layer, format) } - MakeTextureHandleResidentARB :: proc "c" (handle: u64) - { impl_MakeTextureHandleResidentARB(handle) } - MakeImageHandleResidentARB :: proc "c" (handle: u64, access: u32) - { impl_MakeImageHandleResidentARB(handle, access) } - MakeTextureHandleNonResidentARB:: proc "c" (handle: u64) - { impl_MakeTextureHandleNonResidentARB(handle) } - MakeImageHandleNonResidentARB :: proc "c" (handle: u64) - { impl_MakeImageHandleNonResidentARB(handle) } + GetTextureHandleARB :: proc "c" (texture: u32) -> u64 { return impl_GetTextureHandleARB(texture) } + GetTextureSamplerHandleARB :: proc "c" (texture, sampler: u32) -> u64 { return impl_GetTextureSamplerHandleARB(texture, sampler) } + GetImageHandleARB :: proc "c" (texture: u32, level: i32, layered: bool, layer: i32, format: u32) -> u64 { return impl_GetImageHandleARB(texture, level, layered, layer, format) } + MakeTextureHandleResidentARB :: proc "c" (handle: u64) { impl_MakeTextureHandleResidentARB(handle) } + MakeImageHandleResidentARB :: proc "c" (handle: u64, access: u32) { impl_MakeImageHandleResidentARB(handle, access) } + MakeTextureHandleNonResidentARB:: proc "c" (handle: u64) { impl_MakeTextureHandleNonResidentARB(handle) } + MakeImageHandleNonResidentARB :: proc "c" (handle: u64) { impl_MakeImageHandleNonResidentARB(handle) } // VERSION_4_1 ReleaseShaderCompiler :: proc "c" () { impl_ReleaseShaderCompiler() } @@ -788,17 +781,17 @@ when !GL_DEBUG { { // add input arguments for arg, arg_index in args[num_ret:] { - if arg_index > 0 { fmt.printf(", ") } + if arg_index > 0 { fmt.printf(", ") } - if v, ok := arg.(u32); ok { // TODO: Assumes all u32 are GLenum (they're not, GLbitfield and GLuint are also mapped to u32), fix later by better typing - if err == .INVALID_ENUM { - fmt.printf("INVALID_ENUM=%d", v) + if v, ok := arg.(u32); ok { // TODO: Assumes all u32 are GLenum (they're not, GLbitfield and GLuint are also mapped to u32), fix later by better typing + if err == .INVALID_ENUM { + fmt.printf("INVALID_ENUM=%d", v) + } else { + fmt.printf("GL_%v=%d", GL_Enum(v), v) + } } else { - fmt.printf("GL_%v=%d", GL_Enum(v), v) + fmt.printf("%v", arg) } - } else { - fmt.printf("%v", arg) - } } // add return arguments @@ -817,7 +810,7 @@ when !GL_DEBUG { } // add location - fmt.printf(" in: %s(%d:%d)\n", from_loc.file_path, from_loc.line, from_loc.column) + fmt.printf(" in: %s(%d:%d)\n", from_loc.file_path, from_loc.line, from_loc.column) } } @@ -1265,20 +1258,13 @@ when !GL_DEBUG { BeginQueryIndexed :: proc "c" (target: u32, index: u32, id: u32, loc := #caller_location) { impl_BeginQueryIndexed(target, index, id); debug_helper(loc, 0, target, index, id) } EndQueryIndexed :: proc "c" (target: u32, index: u32, loc := #caller_location) { impl_EndQueryIndexed(target, index); debug_helper(loc, 0, target, index) } GetQueryIndexediv :: proc "c" (target: u32, index: u32, pname: u32, params: [^]i32, loc := #caller_location) { impl_GetQueryIndexediv(target, index, pname, params); debug_helper(loc, 0, target, index, pname, params) } - GetTextureHandleARB :: proc "c" (target: u32, loc := #caller_location) -> u64 - { ret := impl_GetTextureHandleARB(target); debug_helper(loc, 0, target); return ret } - GetTextureSamplerHandleARB :: proc "c" (texture, sampler: u32, loc := #caller_location) -> u64 - { ret := impl_GetTextureSamplerHandleARB(texture, sampler); debug_helper(loc, 0, texture, sampler); return ret } - GetImageHandleARB :: proc "c" (texture: u32, level: i32, layered: bool, layer: i32, format: u32, loc := #caller_location) -> u64 - { ret := impl_GetImageHandleARB(texture, level, layered, layer, format); debug_helper(loc, 0, texture, level, layered, layer, format); return ret } - MakeTextureHandleResidentARB :: proc "c" (handle: u64, loc := #caller_location) - { impl_MakeTextureHandleResidentARB(handle); debug_helper(loc, 0, handle) } - MakeImageHandleResidentARB :: proc "c" (handle: u64, access: u32, loc := #caller_location) - { impl_MakeImageHandleResidentARB(handle, access); debug_helper(loc, 0, handle, access) } - MakeTextureHandleNonResidentARB:: proc "c" (handle: u64, loc := #caller_location) - { impl_MakeTextureHandleNonResidentARB(handle); debug_helper(loc, 0, handle) } - MakeImageHandleNonResidentARB :: proc "c" (handle: u64, loc := #caller_location) - { impl_MakeImageHandleNonResidentARB(handle); debug_helper(loc, 0, handle) } + GetTextureHandleARB :: proc "c" (target: u32, loc := #caller_location) -> u64 { ret := impl_GetTextureHandleARB(target); debug_helper(loc, 0, target); return ret } + GetTextureSamplerHandleARB :: proc "c" (texture, sampler: u32, loc := #caller_location) -> u64 { ret := impl_GetTextureSamplerHandleARB(texture, sampler); debug_helper(loc, 0, texture, sampler); return ret } + GetImageHandleARB :: proc "c" (texture: u32, level: i32, layered: bool, layer: i32, format: u32, loc := #caller_location) -> u64 { ret := impl_GetImageHandleARB(texture, level, layered, layer, format); debug_helper(loc, 0, texture, level, layered, layer, format); return ret } + MakeTextureHandleResidentARB :: proc "c" (handle: u64, loc := #caller_location) { impl_MakeTextureHandleResidentARB(handle); debug_helper(loc, 0, handle) } + MakeImageHandleResidentARB :: proc "c" (handle: u64, access: u32, loc := #caller_location) { impl_MakeImageHandleResidentARB(handle, access); debug_helper(loc, 0, handle, access) } + MakeTextureHandleNonResidentARB:: proc "c" (handle: u64, loc := #caller_location) { impl_MakeTextureHandleNonResidentARB(handle); debug_helper(loc, 0, handle) } + MakeImageHandleNonResidentARB :: proc "c" (handle: u64, loc := #caller_location) { impl_MakeImageHandleNonResidentARB(handle); debug_helper(loc, 0, handle) } diff --git a/vendor/cgltf/cgltf.odin b/vendor/cgltf/cgltf.odin index 024e8dfaa..a5d474a7b 100644 --- a/vendor/cgltf/cgltf.odin +++ b/vendor/cgltf/cgltf.odin @@ -1,9 +1,23 @@ package cgltf -when ODIN_OS == .Windows { foreign import lib "lib/cgltf.lib" } -else when ODIN_OS == .Linux { foreign import lib "lib/cgltf.a" } -else when ODIN_OS == .Darwin { foreign import lib "lib/darwin/cgltf.a" } -else { foreign import lib "system:cgltf" } +@(private) +LIB :: ( + "lib/cgltf.lib" when ODIN_OS == .Windows + else "lib/cgltf.a" when ODIN_OS == .Linux + else "lib/darwin/cgltf.a" when ODIN_OS == .Darwin + else "" +) + +when LIB != "" { + when !#exists(LIB) { + // Windows library is shipped with the compiler, so a Windows specific message should not be needed. + #panic("Could not find the compiled cgltf library, it can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/cgltf/src\"`") + } + + foreign import lib { LIB } +} else { + foreign import lib "system:cgltf" +} import "core:c" diff --git a/vendor/commonmark/cmark.odin b/vendor/commonmark/cmark.odin index 9ad71da3f..50544b9bd 100644 --- a/vendor/commonmark/cmark.odin +++ b/vendor/commonmark/cmark.odin @@ -494,15 +494,15 @@ free_cstring :: proc "c" (str: cstring) { free_rawptr(rawptr(str)) } free_string :: proc "c" (s: string) { - free_rawptr(raw_data(s)) + free_rawptr(raw_data(s)) } free :: proc{free_rawptr, free_cstring} // Wrap CMark allocator as Odin allocator @(private) cmark_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> (res: []byte, err: runtime.Allocator_Error) { + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> (res: []byte, err: runtime.Allocator_Error) { cmark_alloc := cast(^Allocator)allocator_data switch mode { diff --git a/vendor/darwin/Metal/MetalClasses.odin b/vendor/darwin/Metal/MetalClasses.odin index ea1711bbc..2d681b0ee 100644 --- a/vendor/darwin/Metal/MetalClasses.odin +++ b/vendor/darwin/Metal/MetalClasses.odin @@ -4949,12 +4949,12 @@ CommandQueue_commandBuffer :: #force_inline proc "c" (self: ^CommandQueue) -> ^C return msgSend(^CommandBuffer, self, "commandBuffer") } @(objc_type=CommandQueue, objc_name="commandBufferWithDescriptor") -CommandQueue_commandBufferWithDescriptor :: #force_inline proc "c" (self: ^CommandQueue, descriptor: ^CommandBufferDescriptor) -> ^CommandQueue { - return msgSend(^CommandQueue, self, "commandBufferWithDescriptor:", descriptor) +CommandQueue_commandBufferWithDescriptor :: #force_inline proc "c" (self: ^CommandQueue, descriptor: ^CommandBufferDescriptor) -> ^CommandBuffer { + return msgSend(^CommandBuffer, self, "commandBufferWithDescriptor:", descriptor) } @(objc_type=CommandQueue, objc_name="commandBufferWithUnretainedReferences") -CommandQueue_commandBufferWithUnretainedReferences :: #force_inline proc "c" (self: ^CommandQueue) -> ^CommandQueue { - return msgSend(^CommandQueue, self, "commandBufferWithUnretainedReferences") +CommandQueue_commandBufferWithUnretainedReferences :: #force_inline proc "c" (self: ^CommandQueue) -> ^CommandBuffer { + return msgSend(^CommandBuffer, self, "commandBufferWithUnretainedReferences") } @(objc_type=CommandQueue, objc_name="device") CommandQueue_device :: #force_inline proc "c" (self: ^CommandQueue) -> ^Device { diff --git a/vendor/directx/d3d11/d3d11.odin b/vendor/directx/d3d11/d3d11.odin index a1e3cf039..83801e854 100644 --- a/vendor/directx/d3d11/d3d11.odin +++ b/vendor/directx/d3d11/d3d11.odin @@ -3374,7 +3374,7 @@ CREATE_DEVICE_FLAG :: enum u32 { DEBUGGABLE = 6, PREVENT_ALTERING_LAYER_SETTINGS_FROM_REGISTRY = 7, DISABLE_GPU_TIMEOUT = 8, - VIDEO_SUPPORT = 12, + VIDEO_SUPPORT = 11, } PFN_CREATE_DEVICE :: #type proc "c" (a0: ^dxgi.IAdapter, a1: DRIVER_TYPE, a2: HMODULE, a3: u32, a4: ^FEATURE_LEVEL, a5: u32, a6: u32, a7: ^^IDevice, a8: ^FEATURE_LEVEL, a9: ^^IDeviceContext) -> HRESULT @@ -3775,39 +3775,41 @@ MESSAGE :: struct { IInfoQueue_VTable :: struct { using iunkown_vtable: IUnknown_VTable, - AddApplicationMessage: proc "system" (this: ^IInfoQueue, Severity: MESSAGE_SEVERITY, pDescription: cstring) -> HRESULT, - AddMessage: proc "system" (this: ^IInfoQueue, Category: MESSAGE_CATEGORY, Severity: MESSAGE_SEVERITY, ID: MESSAGE_ID, pDescription: cstring) -> HRESULT, - AddRetrievalFilterEntries: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER) -> HRESULT, - AddStorageFilterEntries: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER) -> HRESULT, - ClearRetrievalFilter: proc "system" (this: ^IInfoQueue), - ClearStorageFilter: proc "system" (this: ^IInfoQueue), + SetMessageCountLimit: proc "system" (this: ^IInfoQueue, MessageCountLimit: u64) -> HRESULT, ClearStoredMessages: proc "system" (this: ^IInfoQueue), - GetBreakOnCategory: proc "system" (this: ^IInfoQueue, Category: MESSAGE_CATEGORY) -> BOOL, - GetBreakOnID: proc "system" (this: ^IInfoQueue, ID: MESSAGE_ID) -> BOOL, - GetBreakOnSeverity: proc "system" (this: ^IInfoQueue, Severity: MESSAGE_SEVERITY) -> BOOL, GetMessage: proc "system" (this: ^IInfoQueue, MessageIndex: u64, pMessage: ^MESSAGE, pMessageByteLength: ^SIZE_T) -> HRESULT, - GetMessageCountLimit: proc "system" (this: ^IInfoQueue) -> u64, - GetMuteDebugOutput: proc "system" (this: ^IInfoQueue) -> BOOL, GetNumMessagesAllowedByStorageFilter: proc "system" (this: ^IInfoQueue) -> u64, GetNumMessagesDeniedByStorageFilter: proc "system" (this: ^IInfoQueue) -> u64, - GetNumMessagesDiscardedByMessageCountLimit: proc "system" (this: ^IInfoQueue) -> u64, GetNumStoredMessages: proc "system" (this: ^IInfoQueue) -> u64, GetNumStoredMessagesAllowedByRetrievalFilter: proc "system" (this: ^IInfoQueue) -> u64, - GetRetrievalFilter: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER, pFilterByteLength: ^SIZE_T) -> HRESULT, - GetRetrievalFilterStackSize: proc "system" (this: ^IInfoQueue) -> u64, + GetNumMessagesDiscardedByMessageCountLimit: proc "system" (this: ^IInfoQueue) -> u64, + GetMessageCountLimit: proc "system" (this: ^IInfoQueue) -> u64, + AddStorageFilterEntries: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER) -> HRESULT, GetStorageFilter: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER, pFilterByteLength: ^SIZE_T) -> HRESULT, - GetStorageFilterStackSize: proc "system" (this: ^IInfoQueue) -> u64, - PopRetrievalFilter: proc "system" (this: ^IInfoQueue), - PopStorageFilter: proc "system" (this: ^IInfoQueue), - PushCopyOfRetrievalFilter: proc "system" (this: ^IInfoQueue) -> HRESULT, - PushCopyOfStorageFilter: proc "system" (this: ^IInfoQueue) -> HRESULT, - PushEmptyRetrievalFilter: proc "system" (this: ^IInfoQueue) -> HRESULT, + ClearStorageFilter: proc "system" (this: ^IInfoQueue), PushEmptyStorageFilter: proc "system" (this: ^IInfoQueue) -> HRESULT, + PushCopyOfStorageFilter: proc "system" (this: ^IInfoQueue) -> HRESULT, + PushStorageFilter: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER) -> HRESULT, + PopStorageFilter: proc "system" (this: ^IInfoQueue), + GetStorageFilterStackSize: proc "system" (this: ^IInfoQueue) -> u64, + AddRetrievalFilterEntries: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER) -> HRESULT, + GetRetrievalFilter: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER, pFilterByteLength: ^SIZE_T) -> HRESULT, + ClearRetrievalFilter: proc "system" (this: ^IInfoQueue), + PushEmptyRetrievalFilter: proc "system" (this: ^IInfoQueue) -> HRESULT, + PushCopyOfRetrievalFilter: proc "system" (this: ^IInfoQueue) -> HRESULT, + PushRetrievalFilter: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER) -> HRESULT, + PopRetrievalFilter: proc "system" (this: ^IInfoQueue), + GetRetrievalFilterStackSize: proc "system" (this: ^IInfoQueue) -> u64, + AddMessage: proc "system" (this: ^IInfoQueue, Category: MESSAGE_CATEGORY, Severity: MESSAGE_SEVERITY, ID: MESSAGE_ID, pDescription: cstring) -> HRESULT, + AddApplicationMessage: proc "system" (this: ^IInfoQueue, Severity: MESSAGE_SEVERITY, pDescription: cstring) -> HRESULT, SetBreakOnCategory: proc "system" (this: ^IInfoQueue, Category: MESSAGE_CATEGORY, bEnable: BOOL) -> HRESULT, - SetBreakOnID: proc "system" (this: ^IInfoQueue, ID: MESSAGE_ID, bEnable: BOOL) -> HRESULT, SetBreakOnSeverity: proc "system" (this: ^IInfoQueue, Severity: MESSAGE_SEVERITY, bEnable: BOOL) -> HRESULT, - SetMessageCountLimit: proc "system" (this: ^IInfoQueue, MessageCountLimit: u64) -> HRESULT, + SetBreakOnID: proc "system" (this: ^IInfoQueue, ID: MESSAGE_ID, bEnable: BOOL) -> HRESULT, + GetBreakOnCategory: proc "system" (this: ^IInfoQueue, Category: MESSAGE_CATEGORY) -> BOOL, + GetBreakOnSeverity: proc "system" (this: ^IInfoQueue, Severity: MESSAGE_SEVERITY) -> BOOL, + GetBreakOnID: proc "system" (this: ^IInfoQueue, ID: MESSAGE_ID) -> BOOL, SetMuteDebugOutput: proc "system" (this: ^IInfoQueue, bMute: BOOL), + GetMuteDebugOutput: proc "system" (this: ^IInfoQueue) -> BOOL, } MESSAGE_ID :: enum u32 { diff --git a/vendor/directx/d3d12/d3d12.odin b/vendor/directx/d3d12/d3d12.odin index 7c4065d8b..e33845b73 100644 --- a/vendor/directx/d3d12/d3d12.odin +++ b/vendor/directx/d3d12/d3d12.odin @@ -82,6 +82,7 @@ FEATURE_LEVEL :: enum i32 { _11_1 = 45312, _12_0 = 49152, _12_1 = 49408, + _12_2 = 49664, } PRIMITIVE_TOPOLOGY :: enum i32 { @@ -833,6 +834,9 @@ FEATURE :: enum i32 { OPTIONS7 = 32, PROTECTED_RESOURCE_SESSION_TYPE_COUNT = 33, PROTECTED_RESOURCE_SESSION_TYPES = 34, + OPTIONS8 = 36, + OPTIONS9 = 37, + WAVE_MMA = 38, } SHADER_MIN_PRECISION_SUPPORT :: enum i32 { @@ -1017,6 +1021,7 @@ SHADER_MODEL :: enum i32 { _6_4 = 100, _6_5 = 101, _6_6 = 102, + _6_7 = 103, } FEATURE_DATA_SHADER_MODEL :: struct { @@ -1052,6 +1057,7 @@ SHADER_CACHE_SUPPORT_FLAG :: enum u32 { LIBRARY = 1, AUTOMATIC_INPROC_CACHE = 2, AUTOMATIC_DISK_CACHE = 3, + DRIVER_MANAGED_CACHE = 4, } FEATURE_DATA_SHADER_CACHE :: struct { @@ -1171,6 +1177,55 @@ FEATURE_DATA_QUERY_META_COMMAND :: struct { QueryOutputDataSizeInBytes: SIZE_T, } +FEATURE_DATA_OPTIONS8 :: struct { + UnalignedBlockTexturesSupported: BOOL, +} + +WAVE_MMA_TIER :: enum i32 { + NOT_SUPPORTED = 0, + _1_0 = 10, +} + +FEATURE_DATA_OPTIONS9 :: struct { + MeshShaderPipelineStatsSupported: BOOL, + MeshShaderSupportsFullRangeRenderTargetArrayIndex: BOOL, + AtomicInt64OnTypedResourceSupported: BOOL, + AtomicInt64OnGroupSharedSupported: BOOL, + DerivativesInMeshAndAmplificationShadersSupported: BOOL, + WaveMMATier: WAVE_MMA_TIER, +} + +WAVE_MMA_INPUT_DATATYPE :: enum i32 { + INVALID = 0, + BYTE = 1, + FLOAT16 = 2, + FLOAT = 3, +} + +WAVE_MMA_DIMENSION :: enum i32 { + INVALID = 0, + _16 = 1, + _64 = 2, +} + +WAVE_MMA_ACCUM_DATATYPE :: enum i32 { + NONE = 0, + INT32 = 1, + FLOAT16 = 2, + FLOAT = 4, +} + +FEATURE_DATA_WAVE_MMA :: struct { + InputDataType: WAVE_MMA_INPUT_DATATYPE, + M: WAVE_MMA_DIMENSION, + N: WAVE_MMA_DIMENSION, + Supported: BOOL, + K: u32, + AccumDataTypes: WAVE_MMA_ACCUM_DATATYPE, + RequiredWaveLaneCountMin: u32, + RequiredWaveLaneCountMax: u32, +} + RESOURCE_ALLOCATION_INFO :: struct { SizeInBytes: u64, Alignment: u64, @@ -1261,6 +1316,7 @@ RESOURCE_FLAG :: enum u32 { ALLOW_CROSS_ADAPTER = 4, ALLOW_SIMULTANEOUS_ACCESS = 5, VIDEO_DECODE_REFERENCE_ONLY = 6, + VIDEO_ENCODE_REFERENCE_ONLY = 7, } MIP_REGION :: struct { @@ -2140,6 +2196,7 @@ QUERY_HEAP_TYPE :: enum i32 { SO_STATISTICS = 3, VIDEO_DECODE_STATISTICS = 4, COPY_QUEUE_TIMESTAMP = 5, + PIPELINE_STATISTICS1 = 7, } QUERY_HEAP_DESC :: struct { @@ -2158,6 +2215,7 @@ QUERY_TYPE :: enum i32 { SO_STATISTICS_STREAM2 = 6, SO_STATISTICS_STREAM3 = 7, VIDEO_DECODE_STATISTICS = 8, + PIPELINE_STATISTICS1 = 10, } PREDICATION_OP :: enum i32 { @@ -2179,6 +2237,23 @@ QUERY_DATA_PIPELINE_STATISTICS :: struct { CSInvocations: u64, } +QUERY_DATA_PIPELINE_STATISTICS1 :: struct { + IAVertices: u64, + IAPrimitives: u64, + VSInvocations: u64, + GSInvocations: u64, + GSPrimitives: u64, + CInvocations: u64, + CPrimitives: u64, + PSInvocations: u64, + HSInvocations: u64, + DSInvocations: u64, + CSInvocations: u64, + ASInvocations: u64, + MSInvocations: u64, + MSPrimitives: u64, +} + QUERY_DATA_SO_STATISTICS :: struct { NumPrimitivesWritten: u64, PrimitivesStorageNeeded: u64, @@ -3244,6 +3319,8 @@ AUTO_BREADCRUMB_OP :: enum i32 { INITIALIZEEXTENSIONCOMMAND = 40, EXECUTEEXTENSIONCOMMAND = 41, DISPATCHMESH = 42, + ENCODEFRAME = 43, + RESOLVEENCODEROUTPUTMETADATA = 44, } AUTO_BREADCRUMB_NODE :: struct { @@ -3283,6 +3360,7 @@ DRED_VERSION :: enum i32 { _1_0 = 1, _1_1 = 2, _1_2 = 3, + _1_3 = 4, } DRED_FLAGS :: distinct bit_set[DRED_FLAG; u32] @@ -3329,6 +3407,8 @@ DRED_ALLOCATION_TYPE :: enum i32 { VIDEO_MOTION_ESTIMATOR = 45, VIDEO_MOTION_VECTOR_HEAP = 46, VIDEO_EXTENSION_COMMAND = 47, + VIDEO_ENCODER = 48, + VIDEO_ENCODER_HEAP = 49, INVALID = -1, } @@ -3367,6 +3447,24 @@ DRED_PAGE_FAULT_OUTPUT1 :: struct { pHeadRecentFreedAllocationNode: ^DRED_ALLOCATION_NODE1, } +DRED_PAGE_FAULT_FLAGS :: bit_set[DRED_PAGE_FAULT_FLAG;u32] +DRED_PAGE_FAULT_FLAG :: enum u32 { +} + +DRED_DEVICE_STATE :: enum i32 { + UNKNOWN = 0, + HUNG = 3, + FAULT = 6, + PAGEFAULT = 7, +} + +DRED_PAGE_FAULT_OUTPUT2 :: struct { + PageFaultVA: GPU_VIRTUAL_ADDRESS, + pHeadExistingAllocationNode: ^DRED_ALLOCATION_NODE1, + pHeadRecentFreedAllocationNode: ^DRED_ALLOCATION_NODE1, + PageFaultFlags: DRED_PAGE_FAULT_FLAGS, +} + DEVICE_REMOVED_EXTENDED_DATA1 :: struct { DeviceRemovedReason: HRESULT, AutoBreadcrumbsOutput: DRED_AUTO_BREADCRUMBS_OUTPUT, @@ -3379,12 +3477,20 @@ DEVICE_REMOVED_EXTENDED_DATA2 :: struct { PageFaultOutput: DRED_PAGE_FAULT_OUTPUT1, } +DEVICE_REMOVED_EXTENDED_DATA3 :: struct { + DeviceRemovedReason: HRESULT, + AutoBreadcrumbsOutput: DRED_AUTO_BREADCRUMBS_OUTPUT1, + PageFaultOutput: DRED_PAGE_FAULT_OUTPUT2, + DeviceState: DRED_DEVICE_STATE, +} + VERSIONED_DEVICE_REMOVED_EXTENDED_DATA :: struct { Version: DRED_VERSION, using _: struct #raw_union { Dred_1_0: DEVICE_REMOVED_EXTENDED_DATA, Dred_1_1: DEVICE_REMOVED_EXTENDED_DATA1, Dred_1_2: DEVICE_REMOVED_EXTENDED_DATA2, + Dred_1_3: DEVICE_REMOVED_EXTENDED_DATA3, }, } @@ -3440,6 +3546,18 @@ IDeviceRemovedExtendedData1_VTable :: struct { GetPageFaultAllocationOutput1: proc "system" (this: ^IDeviceRemovedExtendedData1, pOutput: ^DRED_PAGE_FAULT_OUTPUT1) -> HRESULT, } +IDeviceRemovedExtendedData2_UUID_STRING :: "67FC5816-E4CA-4915-BF18-42541272DA54" +IDeviceRemovedExtendedData2_UUID := &IID{0x67FC5816, 0xE4CA, 0x4915, {0xBF, 0x18, 0x42, 0x54, 0x12, 0x72, 0xDA, 0x54}} +IDeviceRemovedExtendedData2 :: struct #raw_union { + #subtype id3d12deviceremovedextendeddata1: IDeviceRemovedExtendedData1, + using id3d12deviceremovedextendeddata2_vtable: ^IDeviceRemovedExtendedData2_VTable, +} +IDeviceRemovedExtendedData2_VTable :: struct { + using id3d12deviceremovedextendeddata1_vtable: IDeviceRemovedExtendedData1_VTable, + GetPageFaultAllocationOutput2: proc "system" (this: ^IDeviceRemovedExtendedData2, pOutput: ^DRED_PAGE_FAULT_OUTPUT2) -> HRESULT, + GetDeviceState: proc "system" (this: ^IDeviceRemovedExtendedData2) -> DRED_DEVICE_STATE, +} + BACKGROUND_PROCESSING_MODE :: enum i32 { ALLOWED = 0, ALLOW_INTRUSIVE_MEASUREMENTS = 1, @@ -3686,6 +3804,71 @@ IGraphicsCommandList4_VTable :: struct { } +SHADER_CACHE_MODE :: enum i32 { + MEMORY = 0, + DISK = 1, +} + +SHADER_CACHE_FLAGS :: bit_set[SHADER_CACHE_FLAG;u32] +SHADER_CACHE_FLAG :: enum u32 { + DRIVER_VERSIONED = 0, + USE_WORKING_DIR = 1, +} + +SHADER_CACHE_SESSION_DESC :: struct { + Identifier: GUID, + Mode: SHADER_CACHE_MODE, + Flags: SHADER_CACHE_FLAGS, + MaximumInMemoryCacheSizeBytes: u32, + MaximumInMemoryCacheEntries: u32, + MaximumValueFileSizeBytes: u32, + Version: u64, +} + +IShaderCacheSession_UUID_STRING :: "28E2495D-0F64-4AE4-A6EC-129255DC49A8" +IShaderCacheSession_UUID := &IID{0x28E2495D, 0x0F64, 0x4AE4, {0xA6, 0xEC, 0x12, 0x92, 0x55, 0xDC, 0x49, 0xA8}} +IShaderCacheSession :: struct #raw_union { + #subtype idevicechild: IDeviceChild, + using id3d12shadercachesession_vtable: ^IShaderCacheSession_VTable, +} +IShaderCacheSession_VTable :: struct { + using idevicechild_vtable: IDeviceChild_VTable, + FindValue: proc "system" (this: ^IShaderCacheSession, pKey: rawptr, KeySize: u32, pValue: rawptr, pValueSize: ^u32) -> HRESULT, + StoreValue: proc "system" (this: ^IShaderCacheSession, pKey: rawptr, KeySize: u32, pValue: rawptr, ValueSize: u32) -> HRESULT, + SetDeleteOnDestroy: proc "system" (this: ^IShaderCacheSession), + GetDesc: proc "system" (this: ^IShaderCacheSession, pRetValue: ^SHADER_CACHE_SESSION_DESC) -> ^SHADER_CACHE_SESSION_DESC, +} + + +SHADER_CACHE_KIND_FLAGS :: bit_set[SHADER_CACHE_KIND_FLAG;u32] +SHADER_CACHE_KIND_FLAG :: enum u32 { + IMPLICIT_D3D_CACHE_FOR_DRIVER = 0, + IMPLICIT_D3D_CONVERSIONS = 1, + IMPLICIT_DRIVER_MANAGED = 2, + APPLICATION_MANAGED = 3, +} + +SHADER_CACHE_CONTROL_FLAGS :: bit_set[SHADER_CACHE_CONTROL_FLAG;u32] +SHADER_CACHE_CONTROL_FLAG :: enum u32 { + DISABLE = 0, + ENABLE = 1, + CLEAR = 2, +} + +IDevice9_UUID_STRING :: "4C80E962-F032-4F60-BC9E-EBC2CFA1D83C" +IDevice9_UUID := &IID{0x4C80E962, 0xF032, 0x4F60, {0xBC, 0x9E, 0xEB, 0xC2, 0xCF, 0xA1, 0xD8, 0x3C}} +IDevice9 :: struct #raw_union { + #subtype id3d12device8: IDevice8, + using id3d12device9_vtable: ^IDevice9_VTable, +} +IDevice9_VTable :: struct { + using id3d12device8_vtable: IDevice8_VTable, + CreateShaderCacheSession: proc "system" (this: ^IDevice9, pDesc: ^SHADER_CACHE_SESSION_DESC , riid: ^IID, ppvSession: ^rawptr) -> HRESULT, + ShaderCacheControl: proc "system" (this: ^IDevice9, Kinds: SHADER_CACHE_KIND_FLAGS, Control: SHADER_CACHE_CONTROL_FLAGS) -> HRESULT, + CreateCommandQueue1: proc "system" (this: ^IDevice9, pDesc: ^COMMAND_QUEUE_DESC, CreatorID: ^IID, riid: ^IID, ppCommandQueue: ^rawptr) -> HRESULT, +} + + ITools_UUID_STRING :: "7071e1f0-e84b-4b33-974f-12fa49de65c5" ITools_UUID := &IID{0x7071e1f0, 0xe84b, 0x4b33, {0x97, 0x4f, 0x12, 0xfa, 0x49, 0xde, 0x65, 0xc5}} ITools :: struct #raw_union { @@ -3766,6 +3949,30 @@ IDebug3_VTable :: struct { SetGPUBasedValidationFlags: proc "system" (this: ^IDebug3, Flags: GPU_BASED_VALIDATION_FLAGS), } + +IDebug4_UUID_STRING :: "014B816E-9EC5-4A2F-A845-FFBE441CE13A" +IDebug4_UUID := &IID{0x014B816E, 0x9EC5, 0x4A2F, {0xA8, 0x45, 0xFF, 0xBE, 0x44, 0x1C, 0xE1, 0x3A}} +IDebug4 :: struct #raw_union { + #subtype id3d12debug3: IDebug3, + using id3d12debug4_vtable: ^IDebug4_VTable, +} +IDebug4_VTable :: struct { + using id3d12debug3_vtable: IDebug3_VTable, + DisableDebugLayer: proc "system" (this: ^IDebug4), +} + + +IDebug5_UUID_STRING :: "548D6B12-09FA-40E0-9069-5DCD589A52C9" +IDebug5_UUID := &IID{0x548D6B12, 0x09FA, 0x40E0, {0x90, 0x69, 0x5D, 0xCD, 0x58, 0x9A, 0x52, 0xC9}} +IDebug5 :: struct #raw_union { + #subtype id3d12debug4: IDebug4, + using id3d12debug5_vtable: ^IDebug5_VTable, +} +IDebug5_VTable :: struct { + using id3d12debug4_vtable: IDebug4_VTable, + SetEnableAutoName: proc "system" (this: ^IDebug5, Enable: BOOL), +} + RLDO_FLAGS :: distinct bit_set[RLDO_FLAG; u32] RLDO_FLAG :: enum u32 { SUMMARY = 0, @@ -4890,6 +5097,19 @@ IInfoQueue1_VTable :: struct { UnregisterMessageCallback: proc "system" (this: ^IInfoQueue1, pCallbackCookie: u32) -> HRESULT, } + +ISDKConfiguration_UUID_STRING :: "E9EB5314-33AA-42B2-A718-D77F58B1F1C7" +ISDKConfiguration_UUID := &IID{0xE9EB5314, 0x33AA, 0x42B2, {0xA7, 0x18, 0xD7, 0x7F, 0x58, 0xB1, 0xF1, 0xC7}} +ISDKConfiguration :: struct #raw_union { + #subtype iunknown: IUnknown, + using id3d12sdkconfiguration_vtable: ^ISDKConfiguration_VTable, +} +ISDKConfiguration_VTable :: struct { + using iunknown_vtable: IUnknown_VTable, + SetSDKVersion: proc "system" (this: ^ISDKConfiguration, SDKVersion: u32, SDKPath: cstring) -> HRESULT, +} + + PFN_CREATE_DEVICE :: #type proc "c" (a0: ^IUnknown, a1: FEATURE_LEVEL, a2: ^IID, a3: ^rawptr) -> HRESULT PFN_GET_DEBUG_INTERFACE :: #type proc "c" (a0: ^IID, a1: ^rawptr) -> HRESULT diff --git a/vendor/directx/dxc/dxcdef_unix.odin b/vendor/directx/dxc/dxcdef_unix.odin index 12a682310..530d03ff0 100644 --- a/vendor/directx/dxc/dxcdef_unix.odin +++ b/vendor/directx/dxc/dxcdef_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd +//+build linux, darwin, freebsd, openbsd, netbsd package directx_dxc import "core:c" diff --git a/vendor/directx/dxgi/dxgi.odin b/vendor/directx/dxgi/dxgi.odin index 5412747bc..5b6137cee 100644 --- a/vendor/directx/dxgi/dxgi.odin +++ b/vendor/directx/dxgi/dxgi.odin @@ -26,22 +26,20 @@ LONG :: win32.LONG RECT :: win32.RECT POINT :: win32.POINT SIZE :: win32.SIZE +WCHAR :: win32.WCHAR +DWORD :: win32.DWORD -IUnknown :: struct { - using _iunknown_vtable: ^IUnknown_VTable, -} -IUnknown_VTable :: struct { - QueryInterface: proc "system" (this: ^IUnknown, riid: ^IID, ppvObject: ^rawptr) -> HRESULT, - AddRef: proc "system" (this: ^IUnknown) -> ULONG, - Release: proc "system" (this: ^IUnknown) -> ULONG, -} +IUnknown :: win32.IUnknown +IUnknown_VTable :: win32.IUnknown_VTable +LPUNKNOWN :: win32.LPUNKNOWN @(default_calling_convention="system") foreign dxgi { - CreateDXGIFactory :: proc(riid: ^IID, ppFactory: ^rawptr) -> HRESULT --- - CreateDXGIFactory1 :: proc(riid: ^IID, ppFactory: ^rawptr) -> HRESULT --- - CreateDXGIFactory2 :: proc(Flags: CREATE_FACTORY, riid: ^IID, ppFactory: ^rawptr) -> HRESULT --- - DXGIGetDebugInterface1 :: proc(Flags: u32, riid: ^IID, pDebug: ^rawptr) -> HRESULT --- + CreateDXGIFactory :: proc(riid: ^IID, ppFactory: ^rawptr) -> HRESULT --- + CreateDXGIFactory1 :: proc(riid: ^IID, ppFactory: ^rawptr) -> HRESULT --- + CreateDXGIFactory2 :: proc(Flags: CREATE_FACTORY, riid: ^IID, ppFactory: ^rawptr) -> HRESULT --- + DXGIGetDebugInterface1 :: proc(Flags: u32, riid: ^IID, pDebug: ^rawptr) -> HRESULT --- + DeclareAdapterRemovalSupport :: proc() -> HRESULT --- } STANDARD_MULTISAMPLE_QUALITY_PATTERN :: 0xffffffff @@ -617,11 +615,11 @@ IDevice_VTable :: struct { SetGPUThreadPriority: proc "system" (this: ^IDevice, Priority: i32) -> HRESULT, GetGPUThreadPriority: proc "system" (this: ^IDevice, pPriority: ^i32) -> HRESULT, } -ADAPTER_FLAG :: enum u32 { // TODO: convert to bit_set - NONE = 0x0, - REMOTE = 0x1, - SOFTWARE = 0x2, - FORCE_DWORD = 0xffffffff, + +ADAPTER_FLAGS :: bit_set[ADAPTER_FLAG;u32] +ADAPTER_FLAG :: enum u32 { + REMOTE = 0, + SOFTWARE = 1, } ADAPTER_DESC1 :: struct { @@ -634,7 +632,7 @@ ADAPTER_DESC1 :: struct { DedicatedSystemMemory: SIZE_T, SharedSystemMemory: SIZE_T, AdapterLuid: LUID, - Flags: ADAPTER_FLAG, + Flags: ADAPTER_FLAGS, } DISPLAY_COLOR_SPACE :: struct { @@ -911,7 +909,7 @@ ADAPTER_DESC2 :: struct { DedicatedSystemMemory: SIZE_T, SharedSystemMemory: SIZE_T, AdapterLuid: LUID, - Flags: ADAPTER_FLAG, + Flags: ADAPTER_FLAGS, GraphicsPreemptionGranularity: GRAPHICS_PREEMPTION_GRANULARITY, ComputePreemptionGranularity: COMPUTE_PREEMPTION_GRANULARITY, } @@ -1165,6 +1163,199 @@ IAdapter3_VTable :: struct { UnregisterVideoMemoryBudgetChangeNotification: proc "system" (this: ^IAdapter3, dwCookie: u32), } +OUTDUPL_FLAG :: enum i32 { + COMPOSITED_UI_CAPTURE_ONLY = 1, +} + + +IOutput5_UUID_STRING :: "80A07424-AB52-42EB-833C-0C42FD282D98" +IOutput5_UUID := &IID{0x80A07424, 0xAB52, 0x42EB, {0x83, 0x3C, 0x0C, 0x42, 0xFD, 0x28, 0x2D, 0x98}} +IOutput5 :: struct #raw_union { + #subtype idxgioutput4: IOutput4, + using idxgioutput5_vtable: ^IOutput5_VTable, +} +IOutput5_VTable :: struct { + using idxgioutput4_vtable: IOutput4_VTable, + DuplicateOutput1: proc "system" (this: ^IOutput5, pDevice: ^IUnknown, Flags: u32, SupportedFormatsCount: u32, pSupportedFormats: ^FORMAT, ppOutputDuplication: ^^IOutputDuplication) -> HRESULT, +} + +HDR_METADATA_TYPE :: enum i32 { + NONE = 0, + HDR10 = 1, + HDR10PLUS = 2, +} + +HDR_METADATA_HDR10 :: struct { + RedPrimary: [2]u16, + GreenPrimary: [2]u16, + BluePrimary: [2]u16, + WhitePoint: [2]u16, + MaxMasteringLuminance: u32, + MinMasteringLuminance: u32, + MaxContentLightLevel: u16, + MaxFrameAverageLightLevel: u16, +} + +HDR_METADATA_HDR10PLUS :: struct { + Data: [72]byte, +} + + +ISwapChain4_UUID_STRING :: "3D585D5A-BD4A-489E-B1F4-3DBCB6452FFB" +ISwapChain4_UUID := &IID{0x3D585D5A, 0xBD4A, 0x489E, {0xB1, 0xF4, 0x3D, 0xBC, 0xB6, 0x45, 0x2F, 0xFB}} +ISwapChain4 :: struct #raw_union { + #subtype idxgiswapchain3: ISwapChain3, + using idxgiswapchain4_vtable: ^ISwapChain4_VTable, +} +ISwapChain4_VTable :: struct { + using idxgiswapchain3_vtable: ISwapChain3_VTable, + SetHDRMetaData: proc "system" (this: ^ISwapChain4, Type: HDR_METADATA_TYPE, Size: u32, pMetaData: rawptr) -> HRESULT, +} + +OFFER_RESOURCE_FLAGS :: bit_set[OFFER_RESOURCE_FLAG;u32] +OFFER_RESOURCE_FLAG :: enum u32 { + ALLOW_DECOMMIT = 0, +} + +RECLAIM_RESOURCE_RESULTS :: enum i32 { + OK = 0, + DISCARDED = 1, + NOT_COMMITTED = 2, +} + + +IDevice4_UUID_STRING :: "95B4F95F-D8DA-4CA4-9EE6-3B76D5968A10" +IDevice4_UUID := &IID{0x95B4F95F, 0xD8DA, 0x4CA4, {0x9E, 0xE6, 0x3B, 0x76, 0xD5, 0x96, 0x8A, 0x10}} +IDevice4 :: struct #raw_union { + #subtype idxgidevice3: IDevice3, + using idxgidevice4_vtable: ^IDevice4_VTable, +} +IDevice4_VTable :: struct { + using idxgidevice3_vtable: IDevice3_VTable, + OfferResources1: proc "system" (this: ^IDevice4, NumResources: u32, ppResources: ^^IResource, Priority: OFFER_RESOURCE_PRIORITY, Flags: OFFER_RESOURCE_FLAGS) -> HRESULT, + ReclaimResources1: proc "system" (this: ^IDevice4, NumResources: u32, ppResources: ^^IResource, pResults: ^RECLAIM_RESOURCE_RESULTS) -> HRESULT, +} + +FEATURE :: enum i32 { + PRESENT_ALLOW_TEARING = 0, +} + + +IFactory5_UUID_STRING :: "7632e1f5-ee65-4dca-87fd-84cd75f8838d" +IFactory5_UUID := &IID{0x7632e1f5, 0xee65, 0x4dca, {0x87, 0xfd, 0x84, 0xcd, 0x75, 0xf8, 0x83, 0x8d}} +IFactory5 :: struct #raw_union { + #subtype idxgifactory4: IFactory4, + using idxgifactory5_vtable: ^IFactory5_VTable, +} +IFactory5_VTable :: struct { + using idxgifactory4_vtable: IFactory4_VTable, + CheckFeatureSupport: proc "system" (this: ^IFactory5, Feature: FEATURE, pFeatureSupportData: rawptr, FeatureSupportDataSize: u32) -> HRESULT, +} + +ADAPTER_FLAGS3 :: bit_set[ADAPTER_FLAG3;u32] +ADAPTER_FLAG3 :: enum u32 { + REMOTE = 0, + SOFTWARE = 1, + ACG_COMPATIBLE = 3, + SUPPORT_MONITORED_FENCES = 4, + SUPPORT_NON_MONITORED_FENCES = 5, + KEYED_MUTEX_CONFORMANCE = 6, +} + +ADAPTER_DESC3 :: struct { + Description: [128]WCHAR, + VendorId: u32, + DeviceId: u32, + SubSysId: u32, + Revision: u32, + DedicatedVideoMemory: u64, + DedicatedSystemMemory: u64, + SharedSystemMemory: u64, + AdapterLuid: LUID, + Flags: ADAPTER_FLAGS3, + GraphicsPreemptionGranularity: GRAPHICS_PREEMPTION_GRANULARITY, + ComputePreemptionGranularity: COMPUTE_PREEMPTION_GRANULARITY, +} + + +IAdapter4_UUID_STRING :: "3c8d99d1-4fbf-4181-a82c-af66bf7bd24e" +IAdapter4_UUID := &IID{0x3c8d99d1, 0x4fbf, 0x4181, {0xa8, 0x2c, 0xaf, 0x66, 0xbf, 0x7b, 0xd2, 0x4e}} +IAdapter4 :: struct #raw_union { + #subtype idxgiadapter3: IAdapter3, + using idxgiadapter4_vtable: ^IAdapter4_VTable, +} +IAdapter4_VTable :: struct { + using idxgiadapter3_vtable: IAdapter3_VTable, + GetDesc3: proc "system" (this: ^IAdapter4, pDesc: ^ADAPTER_DESC3) -> HRESULT, +} + +OUTPUT_DESC1 :: struct { + DeviceName: [32]WCHAR, + DesktopCoordinates: RECT, + AttachedToDesktop: BOOL, + Rotation: MODE_ROTATION, + Monitor: HMONITOR, + BitsPerColor: u32, + ColorSpace: COLOR_SPACE_TYPE, + RedPrimary: [2]f32, + GreenPrimary: [2]f32, + BluePrimary: [2]f32, + WhitePoint: [2]f32, + MinLuminance: f32, + MaxLuminance: f32, + MaxFullFrameLuminance: f32, +} + +HARDWARE_COMPOSITION_SUPPORT_FLAGS :: bit_set[HARDWARE_COMPOSITION_SUPPORT_FLAG;u32] +HARDWARE_COMPOSITION_SUPPORT_FLAG :: enum u32 { + FULLSCREEN = 0, + WINDOWED = 1, + CURSOR_STRETCHED = 2, +} + + +IOutput6_UUID_STRING :: "068346e8-aaec-4b84-add7-137f513f77a1" +IOutput6_UUID := &IID{0x068346e8, 0xaaec, 0x4b84, {0xad, 0xd7, 0x13, 0x7f, 0x51, 0x3f, 0x77, 0xa1}} +IOutput6 :: struct #raw_union { + #subtype idxgioutput5: IOutput5, + using idxgioutput6_vtable: ^IOutput6_VTable, +} +IOutput6_VTable :: struct { + using idxgioutput5_vtable: IOutput5_VTable, + GetDesc1: proc "system" (this: ^IOutput6, pDesc: ^OUTPUT_DESC1) -> HRESULT, + CheckHardwareCompositionSupport: proc "system" (this: ^IOutput6, pFlags: ^HARDWARE_COMPOSITION_SUPPORT_FLAGS) -> HRESULT, +} + +GPU_PREFERENCE :: enum i32 { + UNSPECIFIED = 0, + MINIMUM_POWER = 1, + HIGH_PERFORMANCE = 2, +} + + +IFactory6_UUID_STRING :: "c1b6694f-ff09-44a9-b03c-77900a0a1d17" +IFactory6_UUID := &IID{0xc1b6694f, 0xff09, 0x44a9, {0xb0, 0x3c, 0x77, 0x90, 0x0a, 0x0a, 0x1d, 0x17}} +IFactory6 :: struct #raw_union { + #subtype idxgifactory5: IFactory5, + using idxgifactory6_vtable: ^IFactory6_VTable, +} +IFactory6_VTable :: struct { + using idxgifactory5_vtable: IFactory5_VTable, + EnumAdapterByGpuPreference: proc "system" (this: ^IFactory6, Adapter: u32, GpuPreference: GPU_PREFERENCE, riid: ^IID, ppvAdapter: ^rawptr) -> HRESULT, +} + +IFactory7_UUID_STRING :: "a4966eed-76db-44da-84c1-ee9a7afb20a8" +IFactory7_UUID := &IID{0xa4966eed, 0x76db, 0x44da, {0x84, 0xc1, 0xee, 0x9a, 0x7a, 0xfb, 0x20, 0xa8}} +IFactory7 :: struct #raw_union { + #subtype idxgifactory6: IFactory6, + using idxgifactory7_vtable: ^IFactory7_VTable, +} +IFactory7_VTable :: struct { + using idxgifactory6_vtable: IFactory6_VTable, + RegisterAdaptersChangedEvent: proc "system" (this: ^IFactory7, hEvent: HANDLE, pdwCookie: ^DWORD) -> HRESULT, + UnregisterAdaptersChangedEvent: proc "system" (this: ^IFactory7, dwCookie: DWORD) -> HRESULT, +} + ERROR_ACCESS_DENIED :: HRESULT(-2005270485) //0x887A002B ERROR_ACCESS_LOST :: HRESULT(-2005270490) //0x887A0026 ERROR_ALREADY_EXISTS :: HRESULT(-2005270474) //0x887A0036 @@ -1192,4 +1383,4 @@ ERROR_WAS_STILL_DRAWING :: HRESULT(-2005270518) //0x887A000A STATUS_OCCLUDED :: HRESULT( 142213121) //0x087A0001 STATUS_MODE_CHANGED :: HRESULT( 142213127) //0x087A0007 -STATUS_MODE_CHANGE_IN_PROGRESS :: HRESULT( 142213128) //0x087A0008 \ No newline at end of file +STATUS_MODE_CHANGE_IN_PROGRESS :: HRESULT( 142213128) //0x087A0008 diff --git a/vendor/egl/egl.odin b/vendor/egl/egl.odin index 3174fa60b..82181b1c5 100644 --- a/vendor/egl/egl.odin +++ b/vendor/egl/egl.odin @@ -47,7 +47,7 @@ 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: ^Context, config_size: i32, num_config: ^i32) -> i32 --- + ChooseConfig :: proc(display: Display, attrib_list: ^i32, configs: ^Config, config_size: i32, num_config: ^i32) -> i32 --- 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 --- diff --git a/vendor/fontstash/fontstash.odin b/vendor/fontstash/fontstash.odin index 70edcd109..2c692db06 100644 --- a/vendor/fontstash/fontstash.odin +++ b/vendor/fontstash/fontstash.odin @@ -1,14 +1,14 @@ -//+build windows, linux, darwin //+vet !using-param package fontstash import "base:runtime" + import "core:log" -import "core:os" import "core:mem" import "core:math" import "core:strings" import "core:slice" + import stbtt "vendor:stb/truetype" // This is a port from Fontstash into odin - specialized for nanovg @@ -321,20 +321,6 @@ __AtlasAddWhiteRect :: proc(ctx: ^FontContext, w, h: int) -> bool { return true } -AddFontPath :: proc( - ctx: ^FontContext, - name: string, - path: string, -) -> int { - data, ok := os.read_entire_file(path) - - if !ok { - log.panicf("FONT: failed to read font at %s", path) - } - - return AddFontMem(ctx, name, data, true) -} - // push a font to the font stack // optionally init with ascii characters at a wanted size AddFontMem :: proc( @@ -798,10 +784,10 @@ __getVerticalAlign :: proc( case .BOTTOMLEFT: switch av { - case .TOP: res = -font.ascender * f32(pixelSize) / 10 - case .MIDDLE: res = -(font.ascender + font.descender) / 2 * f32(pixelSize) / 10 - case .BASELINE: res = 0 - case .BOTTOM: res = -font.descender * f32(pixelSize) / 10 + case .TOP: res = -font.ascender * f32(pixelSize) / 10 + case .MIDDLE: res = -(font.ascender + font.descender) / 2 * f32(pixelSize) / 10 + case .BASELINE: res = 0 + case .BOTTOM: res = -font.descender * f32(pixelSize) / 10 } } @@ -1192,4 +1178,4 @@ EndState :: proc(using ctx: ^FontContext) { } __dirtyRectReset(ctx) } -} \ No newline at end of file +} diff --git a/vendor/fontstash/fontstash_os.odin b/vendor/fontstash/fontstash_os.odin new file mode 100644 index 000000000..6182573bd --- /dev/null +++ b/vendor/fontstash/fontstash_os.odin @@ -0,0 +1,20 @@ +//+build !js +package fontstash + +import "core:log" +import "core:os" + +AddFontPath :: proc( + ctx: ^FontContext, + name: string, + path: string, +) -> int { + data, ok := os.read_entire_file(path) + + if !ok { + log.panicf("FONT: failed to read font at %s", path) + } + + return AddFontMem(ctx, name, data, true) +} + diff --git a/vendor/fontstash/fontstash_other.odin b/vendor/fontstash/fontstash_other.odin new file mode 100644 index 000000000..1c2ca3f28 --- /dev/null +++ b/vendor/fontstash/fontstash_other.odin @@ -0,0 +1,10 @@ +//+build js +package fontstash + +AddFontPath :: proc( + ctx: ^FontContext, + name: string, + path: string, +) -> int { + panic("fontstash.AddFontPath is unsupported on the JS target") +} diff --git a/vendor/glfw/LICENSE.txt b/vendor/glfw/LICENSE.txt new file mode 100644 index 000000000..b8c096845 --- /dev/null +++ b/vendor/glfw/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2002-2006 Marcus Geelnard + +Copyright (c) 2006-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. diff --git a/vendor/glfw/bindings/bindings.odin b/vendor/glfw/bindings/bindings.odin index 164a8ea2d..81569f177 100644 --- a/vendor/glfw/bindings/bindings.odin +++ b/vendor/glfw/bindings/bindings.odin @@ -197,7 +197,12 @@ foreign glfw { SetErrorCallback :: proc(cbfun: ErrorProc) -> ErrorProc --- + // Functions added in 3.4, Linux links against system glfw so we define these as weak to be able + // to check at runtime if they are available. + + @(linkage="weak") GetPlatform :: proc() -> c.int --- + @(linkage="weak") PlatformSupported :: proc(platform: c.int) -> b32 --- } diff --git a/vendor/glfw/native_linux.odin b/vendor/glfw/native_linux.odin index 9b9e14790..acae8a27e 100644 --- a/vendor/glfw/native_linux.odin +++ b/vendor/glfw/native_linux.odin @@ -2,14 +2,24 @@ package glfw -// TODO: Native Linux -// Display* glfwGetX11Display(void); -// RRCrtc glfwGetX11Adapter(GLFWmonitor* monitor); -// RROutput glfwGetX11Monitor(GLFWmonitor* monitor); -// Window glfwGetX11Window(GLFWwindow* window); -// void glfwSetX11SelectionString(const char* string); -// const char* glfwGetX11SelectionString(void); +import "vendor:x11/xlib" -// struct wl_display* glfwGetWaylandDisplay(void); -// struct wl_output* glfwGetWaylandMonitor(GLFWmonitor* monitor); -// struct wl_surface* glfwGetWaylandWindow(GLFWwindow* window); +@(default_calling_convention="c", link_prefix="glfw") +foreign { + GetX11Display :: proc() -> ^xlib.Display --- + GetX11Window :: proc(window: WindowHandle) -> xlib.Window --- + GetX11Adapter :: proc(monitor: MonitorHandle) -> xlib.RRCrtc --- + GetX11Monitor :: proc(monitor: MonitorHandle) -> xlib.RROutput --- + SetX11SelectionString :: proc(string: cstring) --- + GetX11SelectionString :: proc() -> cstring --- + + // Functions added in 3.4, Linux links against system glfw so we define these as weak to be able + // to check at runtime if they are available. + + @(linkage="weak") + GetWaylandDisplay :: proc() -> rawptr /* struct wl_display* */ --- + @(linkage="weak") + GetWaylandWindow :: proc(window: WindowHandle) -> rawptr /* struct wl_surface* */ --- + @(linkage="weak") + GetWaylandMonitor :: proc(monitor: MonitorHandle) -> rawptr /* struct wl_output* */ --- +} diff --git a/vendor/microui/microui.odin b/vendor/microui/microui.odin index 495289ede..08a96acf2 100644 --- a/vendor/microui/microui.odin +++ b/vendor/microui/microui.odin @@ -29,6 +29,7 @@ import "core:sort" import "core:strings" import "core:strconv" import "core:math" +import textedit "core:text/edit" COMMAND_LIST_SIZE :: #config(MICROUI_COMMAND_LIST_SIZE, 256 * 1024) ROOT_LIST_SIZE :: #config(MICROUI_ROOT_LIST_SIZE, 32) @@ -51,6 +52,7 @@ Clip :: enum u32 { Color_Type :: enum u32 { TEXT, + SELECTION_BG, BORDER, WINDOW_BG, TITLE_BG, @@ -111,7 +113,16 @@ Key :: enum u32 { CTRL, ALT, BACKSPACE, + DELETE, RETURN, + LEFT, + RIGHT, + HOME, + END, + A, + X, + C, + V, } Key_Set :: distinct bit_set[Key; u32] @@ -235,6 +246,8 @@ Context :: struct { key_down_bits, key_pressed_bits: Key_Set, _text_store: [MAX_TEXT_STORE]u8, text_input: strings.Builder, // uses `_text_store` as backing store with nil_allocator. + textbox_state: textedit.State, + textbox_offset: i32, } Stack :: struct($T: typeid, $N: int) { @@ -260,6 +273,7 @@ default_style := Style{ scrollbar_size = 12, thumb_size = 8, colors = { .TEXT = {230, 230, 230, 255}, + .SELECTION_BG = {90, 90, 90, 255}, .BORDER = {25, 25, 25, 255}, .WINDOW_BG = {50, 50, 50, 255}, .TITLE_BG = {25, 25, 25, 255}, @@ -305,12 +319,21 @@ default_draw_frame :: proc(ctx: ^Context, rect: Rect, colorid: Color_Type) { } } -init :: proc(ctx: ^Context) { +init :: proc( + ctx: ^Context, + set_clipboard: proc(user_data: rawptr, text: string) -> (ok: bool) = nil, + get_clipboard: proc(user_data: rawptr) -> (text: string, ok: bool) = nil, + clipboard_user_data: rawptr = nil, +) { ctx^ = {} // zero memory ctx.draw_frame = default_draw_frame ctx._style = default_style ctx.style = &ctx._style ctx.text_input = strings.builder_from_bytes(ctx._text_store[:]) + + ctx.textbox_state.set_clipboard = set_clipboard + ctx.textbox_state.get_clipboard = get_clipboard + ctx.textbox_state.clipboard_user_data = clipboard_user_data } begin :: proc(ctx: ^Context) { @@ -599,7 +622,7 @@ push_command :: proc(ctx: ^Context, $Type: typeid, extra_size := 0) -> ^Type { return cmd } -next_command :: proc(ctx: ^Context, pcmd: ^^Command) -> bool { +next_command :: proc "contextless" (ctx: ^Context, pcmd: ^^Command) -> bool { cmd := pcmd^ defer pcmd^ = cmd if cmd != nil { @@ -607,7 +630,7 @@ next_command :: proc(ctx: ^Context, pcmd: ^^Command) -> bool { } else { cmd = (^Command)(&ctx.command_list.items[0]) } - invalid_command :: #force_inline proc(ctx: ^Context) -> ^Command { + invalid_command :: #force_inline proc "contextless" (ctx: ^Context) -> ^Command { return (^Command)(&ctx.command_list.items[ctx.command_list.idx]) } for cmd != invalid_command(ctx) { @@ -620,7 +643,7 @@ next_command :: proc(ctx: ^Context, pcmd: ^^Command) -> bool { return false } -next_command_iterator :: proc(ctx: ^Context, pcm: ^^Command) -> (Command_Variant, bool) { +next_command_iterator :: proc "contextless" (ctx: ^Context, pcm: ^^Command) -> (Command_Variant, bool) { if next_command(ctx, pcm) { return pcm^.variant, true } @@ -967,23 +990,95 @@ checkbox :: proc(ctx: ^Context, label: string, state: ^bool) -> (res: Result_Set textbox_raw :: proc(ctx: ^Context, textbuf: []u8, textlen: ^int, id: Id, r: Rect, opt := Options{}) -> (res: Result_Set) { update_control(ctx, id, r, opt | {.HOLD_FOCUS}) + font := ctx.style.font + if ctx.focus_id == id { + /* create a builder backed by the user's buffer */ + builder := strings.builder_from_bytes(textbuf) + non_zero_resize(&builder.buf, textlen^) + ctx.textbox_state.builder = &builder + if ctx.textbox_state.id != u64(id) { + ctx.textbox_state.id = u64(id) + ctx.textbox_state.selection = {} + } + + /* check selection bounds */ + if ctx.textbox_state.selection[0] > textlen^ || ctx.textbox_state.selection[1] > textlen^ { + ctx.textbox_state.selection = {} + } + /* handle text input */ - n := min(len(textbuf) - textlen^, strings.builder_len(ctx.text_input)) - if n > 0 { - copy(textbuf[textlen^:], strings.to_string(ctx.text_input)[:n]) - textlen^ += n + if strings.builder_len(ctx.text_input) > 0 { + if textedit.input_text(&ctx.textbox_state, strings.to_string(ctx.text_input)) > 0 { + textlen^ = strings.builder_len(builder) + res += {.CHANGE} + } + } + /* handle ctrl+a */ + if .A in ctx.key_pressed_bits && .CTRL in ctx.key_down_bits && .ALT not_in ctx.key_down_bits { + ctx.textbox_state.selection = {textlen^, 0} + } + /* handle ctrl+x */ + if .X in ctx.key_pressed_bits && .CTRL in ctx.key_down_bits && .ALT not_in ctx.key_down_bits { + if textedit.cut(&ctx.textbox_state) { + textlen^ = strings.builder_len(builder) + res += {.CHANGE} + } + } + /* handle ctrl+c */ + if .C in ctx.key_pressed_bits && .CTRL in ctx.key_down_bits && .ALT not_in ctx.key_down_bits { + textedit.copy(&ctx.textbox_state) + } + /* handle ctrl+v */ + if .V in ctx.key_pressed_bits && .CTRL in ctx.key_down_bits && .ALT not_in ctx.key_down_bits { + if textedit.paste(&ctx.textbox_state) { + textlen^ = strings.builder_len(builder) + res += {.CHANGE} + } + } + /* handle left/right */ + if .LEFT in ctx.key_pressed_bits { + move: textedit.Translation = .Word_Left if .CTRL in ctx.key_down_bits else .Left + if .SHIFT in ctx.key_down_bits { + textedit.select_to(&ctx.textbox_state, move) + } else { + textedit.move_to(&ctx.textbox_state, move) + } + } + if .RIGHT in ctx.key_pressed_bits { + move: textedit.Translation = .Word_Right if .CTRL in ctx.key_down_bits else .Right + if .SHIFT in ctx.key_down_bits { + textedit.select_to(&ctx.textbox_state, move) + } else { + textedit.move_to(&ctx.textbox_state, move) + } + } + /* handle home/end */ + if .HOME in ctx.key_pressed_bits { + if .SHIFT in ctx.key_down_bits { + textedit.select_to(&ctx.textbox_state, .Start) + } else { + textedit.move_to(&ctx.textbox_state, .Start) + } + } + if .END in ctx.key_pressed_bits { + if .SHIFT in ctx.key_down_bits { + textedit.select_to(&ctx.textbox_state, .End) + } else { + textedit.move_to(&ctx.textbox_state, .End) + } + } + /* handle backspace/delete */ + if .BACKSPACE in ctx.key_pressed_bits && textlen^ > 0 { + move: textedit.Translation = .Word_Left if .CTRL in ctx.key_down_bits else .Left + textedit.delete_to(&ctx.textbox_state, move) + textlen^ = strings.builder_len(builder) res += {.CHANGE} } - /* handle backspace */ - if .BACKSPACE in ctx.key_pressed_bits && textlen^ > 0 { - /* skip utf-8 continuation bytes */ - for textlen^ > 0 { - textlen^ -= 1 - if textbuf[textlen^] & 0xc0 != 0x80 { - break - } - } + if .DELETE in ctx.key_pressed_bits && textlen^ > 0 { + move: textedit.Translation = .Word_Right if .CTRL in ctx.key_down_bits else .Right + textedit.delete_to(&ctx.textbox_state, move) + textlen^ = strings.builder_len(builder) res += {.CHANGE} } /* handle return */ @@ -991,6 +1086,25 @@ textbox_raw :: proc(ctx: ^Context, textbuf: []u8, textlen: ^int, id: Id, r: Rect set_focus(ctx, 0) res += {.SUBMIT} } + + /* handle click/drag */ + if .LEFT in ctx.mouse_down_bits { + idx := textlen^ + for i in 0..= 0x80 && textbuf[i] < 0xc0 { + continue + } + if ctx.mouse_pos.x < r.x + ctx.textbox_offset + ctx.text_width(font, string(textbuf[:i])) { + idx = i + break + } + } + ctx.textbox_state.selection[0] = idx + if .LEFT in ctx.mouse_pressed_bits && .SHIFT not_in ctx.key_down_bits { + ctx.textbox_state.selection[1] = idx + } + } } textstr := string(textbuf[:textlen^]) @@ -998,16 +1112,21 @@ textbox_raw :: proc(ctx: ^Context, textbuf: []u8, textlen: ^int, id: Id, r: Rect /* draw */ draw_control_frame(ctx, id, r, .BASE, opt) if ctx.focus_id == id { - color := ctx.style.colors[.TEXT] - font := ctx.style.font - textw := ctx.text_width(font, textstr) - texth := ctx.text_height(font) - ofx := r.w - ctx.style.padding - textw - 1 - textx := r.x + min(ofx, ctx.style.padding) - texty := r.y + (r.h - texth) / 2 + text_color := ctx.style.colors[.TEXT] + sel_color := ctx.style.colors[.SELECTION_BG] + textw := ctx.text_width(font, textstr) + texth := ctx.text_height(font) + headx := ctx.text_width(font, textstr[:ctx.textbox_state.selection[0]]) + tailx := ctx.text_width(font, textstr[:ctx.textbox_state.selection[1]]) + ofmin := max(ctx.style.padding - headx, r.w - textw - ctx.style.padding) + ofmax := min(r.w - headx - ctx.style.padding, ctx.style.padding) + ctx.textbox_offset = clamp(ctx.textbox_offset, ofmin, ofmax) + textx := r.x + ctx.textbox_offset + texty := r.y + (r.h - texth) / 2 push_clip_rect(ctx, r) - draw_text(ctx, font, textstr, Vec2{textx, texty}, color) - draw_rect(ctx, Rect{textx + textw, texty, 1, texth}, color) + draw_rect(ctx, Rect{textx + min(headx, tailx), texty, abs(headx - tailx), texth}, sel_color) + draw_text(ctx, font, textstr, Vec2{textx, texty}, text_color) + draw_rect(ctx, Rect{textx + headx, texty, 1, texth}, text_color) pop_clip_rect(ctx) } else { draw_control_text(ctx, textstr, r, .TEXT, opt) diff --git a/vendor/miniaudio/common.odin b/vendor/miniaudio/common.odin index b38599d96..d72c3f251 100644 --- a/vendor/miniaudio/common.odin +++ b/vendor/miniaudio/common.odin @@ -8,12 +8,16 @@ when MINIAUDIO_SHARED { #panic("Shared linking for miniaudio is not supported yet") } -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" +@(private) +LIB :: "lib/miniaudio.lib" when ODIN_OS == .Windows else "lib/miniaudio.a" + +when !#exists(LIB) { + // Windows library is shipped with the compiler, so a Windows specific message should not be needed. + #panic("Could not find the compiled miniaudio library, it can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/miniaudio/src\"`") } +foreign import lib { LIB } + BINDINGS_VERSION_MAJOR :: 0 BINDINGS_VERSION_MINOR :: 11 BINDINGS_VERSION_REVISION :: 21 diff --git a/vendor/miniaudio/data_conversion.odin b/vendor/miniaudio/data_conversion.odin index aee26bc8c..c33f54707 100644 --- a/vendor/miniaudio/data_conversion.odin +++ b/vendor/miniaudio/data_conversion.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* diff --git a/vendor/miniaudio/decoding.odin b/vendor/miniaudio/decoding.odin index 4433aa5a7..4860680c9 100644 --- a/vendor/miniaudio/decoding.odin +++ b/vendor/miniaudio/decoding.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/device_io_procs.odin b/vendor/miniaudio/device_io_procs.odin index 0d572ae2c..21ac1afd7 100644 --- a/vendor/miniaudio/device_io_procs.odin +++ b/vendor/miniaudio/device_io_procs.odin @@ -1,10 +1,6 @@ package miniaudio -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } import "core:c" diff --git a/vendor/miniaudio/effects.odin b/vendor/miniaudio/effects.odin index 273845001..a3710ad88 100644 --- a/vendor/miniaudio/effects.odin +++ b/vendor/miniaudio/effects.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /* Delay diff --git a/vendor/miniaudio/encoding.odin b/vendor/miniaudio/encoding.odin index 63aa45c6d..da8389b06 100644 --- a/vendor/miniaudio/encoding.odin +++ b/vendor/miniaudio/encoding.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/engine.odin b/vendor/miniaudio/engine.odin index 6eabd75c2..ecd3fb39d 100644 --- a/vendor/miniaudio/engine.odin +++ b/vendor/miniaudio/engine.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/filtering.odin b/vendor/miniaudio/filtering.odin index 31ddbd7a4..d1c053d20 100644 --- a/vendor/miniaudio/filtering.odin +++ b/vendor/miniaudio/filtering.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************** diff --git a/vendor/miniaudio/generation.odin b/vendor/miniaudio/generation.odin index 69be85234..746efcca7 100644 --- a/vendor/miniaudio/generation.odin +++ b/vendor/miniaudio/generation.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } waveform_type :: enum c.int { sine, diff --git a/vendor/miniaudio/job_queue.odin b/vendor/miniaudio/job_queue.odin index baa71c5f1..01ee31216 100644 --- a/vendor/miniaudio/job_queue.odin +++ b/vendor/miniaudio/job_queue.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /* Slot Allocator diff --git a/vendor/miniaudio/logging.odin b/vendor/miniaudio/logging.odin index 52b1c7980..afddf8e68 100644 --- a/vendor/miniaudio/logging.odin +++ b/vendor/miniaudio/logging.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c/libc" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } MAX_LOG_CALLBACKS :: 4 diff --git a/vendor/miniaudio/node_graph.odin b/vendor/miniaudio/node_graph.odin index 09ab50a3b..63482413b 100644 --- a/vendor/miniaudio/node_graph.odin +++ b/vendor/miniaudio/node_graph.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/resource_manager.odin b/vendor/miniaudio/resource_manager.odin index f27f3a53a..0284db86b 100644 --- a/vendor/miniaudio/resource_manager.odin +++ b/vendor/miniaudio/resource_manager.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/synchronization.odin b/vendor/miniaudio/synchronization.odin index cd4b0a5f0..012f52c2c 100644 --- a/vendor/miniaudio/synchronization.odin +++ b/vendor/miniaudio/synchronization.odin @@ -1,10 +1,6 @@ package miniaudio -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } @(default_calling_convention="c", link_prefix="ma_") foreign lib { diff --git a/vendor/miniaudio/utilities.odin b/vendor/miniaudio/utilities.odin index d518a514a..8728f40dc 100644 --- a/vendor/miniaudio/utilities.odin +++ b/vendor/miniaudio/utilities.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } @(default_calling_convention="c", link_prefix="ma_") foreign lib { diff --git a/vendor/miniaudio/vfs.odin b/vendor/miniaudio/vfs.odin index 475d118fc..b045a1501 100644 --- a/vendor/miniaudio/vfs.odin +++ b/vendor/miniaudio/vfs.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/raylib/raygui.odin b/vendor/raylib/raygui.odin index 41a4250a1..8cda9c072 100644 --- a/vendor/raylib/raygui.odin +++ b/vendor/raylib/raygui.odin @@ -5,47 +5,21 @@ import "core:c" RAYGUI_SHARED :: #config(RAYGUI_SHARED, false) when ODIN_OS == .Windows { - when RAYGUI_SHARED { - foreign import lib { - "windows/rayguidll.lib", - } - } else { - foreign import lib { - "windows/raygui.lib", - } + foreign import lib { + "windows/rayguidll.lib" when RAYGUI_SHARED else "windows/raygui.lib", } } else when ODIN_OS == .Linux { - when RAYGUI_SHARED { - foreign import lib "linux/libraygui.so" - } else { - foreign import lib "linux/libraygui.a" + foreign import lib { + "linux/libraygui.so" when RAYGUI_SHARED else "linux/libraygui.a", } } else when ODIN_OS == .Darwin { when ODIN_ARCH == .arm64 { - when RAYGUI_SHARED { - foreign import lib { - "macos-arm64/libraygui.dylib", - } - } else { - foreign import lib { - "macos-arm64/libraygui.a", - // "system:Cocoa.framework", - // "system:OpenGL.framework", - // "system:IOKit.framework", - } + foreign import lib { + "macos-arm64/libraygui.dylib" when RAYGUI_SHARED else "macos-arm64/libraygui.a", } } else { - when RAYGUI_SHARED { - foreign import lib { - "macos/libraygui.dylib", - } - } else { - foreign import lib { - "macos/libraygui.a", - // "system:Cocoa.framework", - // "system:OpenGL.framework", - // "system:IOKit.framework", - } + foreign import lib { + "macos/libraygui.dylib" when RAYGUI_SHARED else "macos/libraygui.a", } } } else { @@ -56,8 +30,8 @@ RAYGUI_VERSION :: "4.0" // Style property GuiStyleProp :: struct { - controlId: u16, - propertyId: u16, + controlId: u16, + propertyId: u16, propertyValue: c.int, } @@ -226,7 +200,7 @@ GuiColorPickerProperty :: enum c.int { HUEBAR_SELECTOR_OVERFLOW, // ColorPicker right hue bar selector overflow } -SCROLLBAR_LEFT_SIDE :: 0 +SCROLLBAR_LEFT_SIDE :: 0 SCROLLBAR_RIGHT_SIDE :: 1 //---------------------------------------------------------------------------------- @@ -262,8 +236,8 @@ foreign lib { // Style set/get functions - GuiSetStyle :: proc(control: c.int, property: c.int, value: c.int) --- // Set one style property - GuiGetStyle :: proc(control: c.int, property: c.int) -> c.int --- // Get one style property + GuiSetStyle :: proc(control: GuiControl, property: GuiStyleProp, value: c.int) --- // Set one style property + GuiGetStyle :: proc(control: GuiControl, property: GuiStyleProp) -> c.int --- // Get one style property // Styles loading functions @@ -278,11 +252,11 @@ foreign lib { // Icons functionality - GuiIconText :: proc(iconId: c.int, text: cstring) -> cstring --- // Get text with icon id prepended (if supported) + GuiIconText :: proc(iconId: GuiIconName, text: cstring) -> cstring --- // Get text with icon id prepended (if supported) GuiSetIconScale :: proc(scale: c.int) --- // Set default icon drawing size GuiGetIcons :: proc() -> [^]u32 --- // Get raygui icons data pointer GuiLoadIcons :: proc(fileName: cstring, loadIconsName: bool) -> [^]cstring --- // Load raygui icons file (.rgi) into internal icons data - GuiDrawIcon :: proc(iconId: c.int, posX: c.int, posY: c.int, pixelSize: c.int, color: Color) --- // Draw icon using pixel size at specified position + GuiDrawIcon :: proc(iconId: GuiIconName, posX, posY: c.int, pixelSize: c.int, color: Color) --- // Draw icon using pixel size at specified position // Controls @@ -300,11 +274,11 @@ foreign lib { GuiLabel :: proc(bounds: Rectangle, text: cstring) -> c.int --- // Label control, shows text GuiButton :: proc(bounds: Rectangle, text: cstring) -> bool --- // Button control, returns true when clicked - GuiLabelButton :: proc(bounds: Rectangle, text: cstring) -> bool --- // Label button control, show true when clicked + GuiLabelButton :: proc(bounds: Rectangle, text: cstring) -> bool --- // Label button control, show true when clicked GuiToggle :: proc(bounds: Rectangle, text: cstring, active: ^bool) -> c.int --- // Toggle Button control, returns true when active GuiToggleGroup :: proc(bounds: Rectangle, text: cstring, active: ^c.int) -> c.int --- // Toggle Group control, returns active toggle index GuiToggleSlider :: proc(bounds: Rectangle, text: cstring, active: ^c.int) -> c.int --- - GuiCheckBox :: proc(bounds: Rectangle, text: cstring, checked: ^bool) -> bool --- // Check Box control, returns true when active + GuiCheckBox :: proc(bounds: Rectangle, text: cstring, checked: ^bool) -> bool --- // Check Box control, returns true when active GuiComboBox :: proc(bounds: Rectangle, text: cstring, active: ^c.int) -> c.int --- // Combo Box control, returns selected item index GuiDropdownBox :: proc(bounds: Rectangle, text: cstring, active: ^c.int, editMode: bool) -> bool --- // Dropdown Box control, returns selected item diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index b98770271..cff590b7f 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -97,76 +97,32 @@ MAX_TEXT_BUFFER_LENGTH :: #config(RAYLIB_MAX_TEXT_BUFFER_LENGTH, 1024) RAYLIB_SHARED :: #config(RAYLIB_SHARED, false) when ODIN_OS == .Windows { - when RAYLIB_SHARED { - @(extra_linker_flags="/NODEFAULTLIB:msvcrt") - foreign import lib { - "windows/raylibdll.lib", - "system:Winmm.lib", - "system:Gdi32.lib", - "system:User32.lib", - "system:Shell32.lib", - } - } else { - @(extra_linker_flags="/NODEFAULTLIB:libcmt") - foreign import lib { - "windows/raylib.lib", - "system:Winmm.lib", - "system:Gdi32.lib", - "system:User32.lib", - "system:Shell32.lib", - } + @(extra_linker_flags="/NODEFAULTLIB:" + ("msvcrt" when RAYLIB_SHARED else "libcmt")) + foreign import lib { + "windows/raylibdll.lib" when RAYLIB_SHARED else "windows/raylib.lib" , + "system:Winmm.lib", + "system:Gdi32.lib", + "system:User32.lib", + "system:Shell32.lib", } } else when ODIN_OS == .Linux { - when RAYLIB_SHARED { - foreign import lib { - // Note(bumbread): I'm not sure why in `linux/` folder there are - // multiple copies of raylib.so, but since these bindings are for - // particular version of the library, I better specify it. Ideally, - // though, it's best specified in terms of major (.so.4) - "linux/libraylib.so.500", - "system:dl", - "system:pthread", - } - } else { - foreign import lib { - "linux/libraylib.a", - "system:dl", - "system:pthread", - } + foreign import lib { + // Note(bumbread): I'm not sure why in `linux/` folder there are + // multiple copies of raylib.so, but since these bindings are for + // particular version of the library, I better specify it. Ideally, + // though, it's best specified in terms of major (.so.4) + "linux/libraylib.so.500" when RAYLIB_SHARED else "linux/libraylib.a", + "system:dl", + "system:pthread", } } else when ODIN_OS == .Darwin { - when ODIN_ARCH == .arm64 { - when RAYLIB_SHARED { - foreign import lib { - "macos-arm64/libraylib.500.dylib", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } else { - foreign import lib { - "macos-arm64/libraylib.a", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } - } else { - when RAYLIB_SHARED { - foreign import lib { - "macos/libraylib.500.dylib", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } else { - foreign import lib { - "macos/libraylib.a", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } + foreign import lib { + "macos" + + ("-arm64" when ODIN_ARCH == .arm64 else "") + + "/libraylib" + (".500.dylib" when RAYLIB_SHARED else ".a"), + "system:Cocoa.framework", + "system:OpenGL.framework", + "system:IOKit.framework", } } else { foreign import lib "system:raylib" @@ -951,8 +907,8 @@ foreign lib { SetWindowTitle :: proc(title: cstring) --- // Set title for window (only PLATFORM_DESKTOP and PLATFORM_WEB) SetWindowPosition :: proc(x, y: c.int) --- // Set window position on screen (only PLATFORM_DESKTOP) SetWindowMonitor :: proc(monitor: c.int) --- // Set monitor for the current window - SetWindowMinSize :: proc(width, height: c.int) --- // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) - SetWindowMaxSize :: proc(width, height: c.int) --- // Set window maximum dimensions (for FLAG_WINDOW_RESIZABLE) + SetWindowMinSize :: proc(width, height: c.int) --- // Set window minimum dimensions (for WINDOW_RESIZABLE) + SetWindowMaxSize :: proc(width, height: c.int) --- // Set window maximum dimensions (for WINDOW_RESIZABLE) SetWindowSize :: proc(width, height: c.int) --- // Set window dimensions SetWindowOpacity :: proc(opacity: f32) --- // Set window opacity [0.0f..1.0f] (only PLATFORM_DESKTOP) SetWindowFocused :: proc() --- // Set window focused (only PLATFORM_DESKTOP) @@ -1028,12 +984,14 @@ foreign lib { LoadShader :: proc(vsFileName, fsFileName: cstring) -> Shader --- // Load shader from files and bind default locations LoadShaderFromMemory :: proc(vsCode, fsCode: cstring) -> Shader --- // Load shader from code strings and bind default locations IsShaderReady :: proc(shader: Shader) -> bool --- // Check if a shader is ready - GetShaderLocation :: proc(shader: Shader, uniformName: cstring) -> ShaderLocationIndex --- // Get shader uniform location - GetShaderLocationAttrib :: proc(shader: Shader, attribName: cstring) -> ShaderLocationIndex --- // Get shader attribute location - SetShaderValue :: proc(shader: Shader, locIndex: ShaderLocationIndex, value: rawptr, uniformType: ShaderUniformDataType) --- // Set shader uniform value - SetShaderValueV :: proc(shader: Shader, locIndex: ShaderLocationIndex, value: rawptr, uniformType: ShaderUniformDataType, count: c.int) --- // Set shader uniform value vector - SetShaderValueMatrix :: proc(shader: Shader, locIndex: ShaderLocationIndex, mat: Matrix) --- // Set shader uniform value (matrix 4x4) - SetShaderValueTexture :: proc(shader: Shader, locIndex: ShaderLocationIndex, texture: Texture2D) --- // Set shader uniform value for texture (sampler2d) + GetShaderLocation :: proc(shader: Shader, uniformName: cstring) -> c.int --- // Get shader uniform location + GetShaderLocationAttrib :: proc(shader: Shader, attribName: cstring) -> c.int --- // Get shader attribute location + + // We use #any_int here so we can pass ShaderLocationIndex + SetShaderValue :: proc(shader: Shader, #any_int locIndex: c.int, value: rawptr, uniformType: ShaderUniformDataType) --- // Set shader uniform value + SetShaderValueV :: proc(shader: Shader, #any_int locIndex: c.int, value: rawptr, uniformType: ShaderUniformDataType, count: c.int) --- // Set shader uniform value vector + SetShaderValueMatrix :: proc(shader: Shader, #any_int locIndex: c.int, mat: Matrix) --- // Set shader uniform value (matrix 4x4) + SetShaderValueTexture :: proc(shader: Shader, #any_int locIndex: c.int, texture: Texture2D) --- // Set shader uniform value for texture (sampler2d) UnloadShader :: proc(shader: Shader) --- // Unload shader from GPU memory (VRAM) // Screen-space-related functions @@ -1057,8 +1015,8 @@ foreign lib { SetRandomSeed :: proc(seed: c.uint) --- // Set the seed for the random number generator GetRandomValue :: proc(min, max: c.int) -> c.int --- // Get a random value between min and max (both included) - LoadRandomSequence :: proc(count : c.uint, min, max: c.int) --- // Load random values sequence, no values repeated - UnloadRandomSequence :: proc(sequence : ^c.int) --- // Unload random values sequence + LoadRandomSequence :: proc(count: c.uint, min, max: c.int) --- // Load random values sequence, no values repeated + UnloadRandomSequence :: proc(sequence: ^c.int) --- // Unload random values sequence // Misc. functions TakeScreenshot :: proc(fileName: cstring) --- // Takes a screenshot of current screen (filename extension defines format) @@ -1466,9 +1424,9 @@ foreign lib { LoadUTF8 :: proc(codepoints: [^]rune, length: c.int) -> [^]byte --- // Load UTF-8 text encoded from codepoints array UnloadUTF8 :: proc(text: [^]byte) --- // Unload UTF-8 text encoded from codepoints array - LoadCodepoints :: proc(text: rawptr, count: ^c.int) -> [^]rune --- // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter + LoadCodepoints :: proc(text: cstring, count: ^c.int) -> [^]rune --- // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter UnloadCodepoints :: proc(codepoints: [^]rune) --- // Unload codepoints data from memory - GetCodepointCount :: proc(text : cstring) -> c.int --- // Get total number of codepoints in a UTF-8 encoded string + GetCodepointCount :: proc(text: cstring) -> c.int --- // Get total number of codepoints in a UTF-8 encoded string GetCodepoint :: proc(text: cstring, codepointSize: ^c.int) -> rune --- // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure GetCodepointNext :: proc(text: cstring, codepointSize: ^c.int) -> rune --- // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure GetCodepointPrevious :: proc(text: cstring, codepointSize: ^c.int) -> rune --- // Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure @@ -1709,7 +1667,7 @@ IsGestureDetected :: proc "c" (gesture: Gesture) -> bool { // Text formatting with variables (sprintf style) -TextFormat :: proc(text: cstring, args: ..any) -> cstring { +TextFormat :: proc(text: cstring, args: ..any) -> cstring { @static buffers: [MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH]byte @static index: u32 @@ -1725,7 +1683,7 @@ TextFormat :: proc(text: cstring, args: ..any) -> cstring { } // Text formatting with variables (sprintf style) and allocates (must be freed with 'MemFree') -TextFormatAlloc :: proc(text: cstring, args: ..any) -> cstring { +TextFormatAlloc :: proc(text: cstring, args: ..any) -> cstring { str := fmt.tprintf(string(text), ..args) return strings.clone_to_cstring(str, MemAllocator()) } diff --git a/vendor/raylib/raymath.odin b/vendor/raylib/raymath.odin index 9682ffe4f..eef5c2fcd 100644 --- a/vendor/raylib/raymath.odin +++ b/vendor/raylib/raymath.odin @@ -668,7 +668,7 @@ MatrixLookAt :: proc "c" (eye, target, up: Vector3) -> Matrix { // Get float array of matrix data @(require_results) MatrixToFloatV :: proc "c" (mat: Matrix) -> [16]f32 { - return transmute([16]f32)mat + return transmute([16]f32)linalg.transpose(mat) } diff --git a/vendor/raylib/rlgl.odin b/vendor/raylib/rlgl.odin deleted file mode 100644 index 97ab0fd07..000000000 --- a/vendor/raylib/rlgl.odin +++ /dev/null @@ -1,564 +0,0 @@ -/********************************************************************************************** -* -* rlgl v5.0 - A multi-OpenGL abstraction layer with an immediate-mode style API -* -* DESCRIPTION: -* An abstraction layer for multiple OpenGL versions (1.1, 2.1, 3.3 Core, 4.3 Core, ES 2.0) -* that provides a pseudo-OpenGL 1.1 immediate-mode style API (rlVertex, rlTranslate, rlRotate...) -* -* ADDITIONAL NOTES: -* When choosing an OpenGL backend different than OpenGL 1.1, some internal buffer are -* initialized on rlglInit() to accumulate vertex data. -* -* When an internal state change is required all the stored vertex data is renderer in batch, -* additionally, rlDrawRenderBatchActive() could be called to force flushing of the batch. -* -* Some resources are also loaded for convenience, here the complete list: -* - Default batch (RLGL.defaultBatch): RenderBatch system to accumulate vertex data -* - Default texture (RLGL.defaultTextureId): 1x1 white pixel R8G8B8A8 -* - Default shader (RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs) -* -* Internal buffer (and resources) must be manually unloaded calling rlglClose(). -* -* CONFIGURATION: -* #define GRAPHICS_API_OPENGL_11 -* #define GRAPHICS_API_OPENGL_21 -* #define GRAPHICS_API_OPENGL_33 -* #define GRAPHICS_API_OPENGL_43 -* #define GRAPHICS_API_OPENGL_ES2 -* #define GRAPHICS_API_OPENGL_ES3 -* Use selected OpenGL graphics backend, should be supported by platform -* Those preprocessor defines are only used on rlgl module, if OpenGL version is -* required by any other module, use rlGetVersion() to check it -* -* #define RLGL_IMPLEMENTATION -* Generates the implementation of the library into the included file. -* If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation. -* -* #define RLGL_RENDER_TEXTURES_HINT -* Enable framebuffer objects (fbo) support (enabled by default) -* Some GPUs could not support them despite the OpenGL version -* -* #define RLGL_SHOW_GL_DETAILS_INFO -* Show OpenGL extensions and capabilities detailed logs on init -* -* #define RLGL_ENABLE_OPENGL_DEBUG_CONTEXT -* Enable debug context (only available on OpenGL 4.3) -* -* rlgl capabilities could be customized just defining some internal -* values before library inclusion (default values listed): -* -* #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 // Default internal render batch elements limits -* #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) -* #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) -* #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) -* -* #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack -* #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported -* #define RL_CULL_DISTANCE_NEAR 0.01 // Default projection matrix near cull distance -* #define RL_CULL_DISTANCE_FAR 1000.0 // Default projection matrix far cull distance -* -* When loading a shader, the following vertex attributes and uniform -* location names are tried to be set automatically: -* -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: 0 -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: 1 -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: 2 -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: 3 -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: 4 -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: 5 -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView)) -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) -* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) -* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) -* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) -* -* DEPENDENCIES: -* - OpenGL libraries (depending on platform and OpenGL version selected) -* - GLAD OpenGL extensions loading library (only for OpenGL 3.3 Core, 4.3 Core) -* -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2014-2023 Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - - -package raylib - -import "core:c" - -RLGL_VERSION :: "4.5" - -when ODIN_OS == .Windows { - foreign import lib { - "windows/raylib.lib", - "system:Winmm.lib", - "system:Gdi32.lib", - "system:User32.lib", - "system:Shell32.lib", - } -} else when ODIN_OS == .Linux { - foreign import lib "linux/libraylib.a" -} else when ODIN_OS == .Darwin { - when ODIN_ARCH == .arm64 { - foreign import lib { - "macos-arm64/libraylib.a", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } else { - foreign import lib { - "macos/libraylib.a", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } -} else { - foreign import lib "system:raylib" -} - -RL_GRAPHICS_API_OPENGL_11 :: false -RL_GRAPHICS_API_OPENGL_21 :: true -RL_GRAPHICS_API_OPENGL_33 :: RL_GRAPHICS_API_OPENGL_21 // default currently -RL_GRAPHICS_API_OPENGL_ES2 :: false -RL_GRAPHICS_API_OPENGL_43 :: false -RL_GRAPHICS_API_OPENGL_ES3 :: false - -when RL_GRAPHICS_API_OPENGL_ES3 { - RL_GRAPHICS_API_OPENGL_ES2 :: true -} - -when !RL_GRAPHICS_API_OPENGL_ES2 { - // This is the maximum amount of elements (quads) per batch - // NOTE: Be careful with text, every letter maps to a quad - RL_DEFAULT_BATCH_BUFFER_ELEMENTS :: 8192 -} else { - // We reduce memory sizes for embedded systems (RPI and HTML5) - // NOTE: On HTML5 (emscripten) this is allocated on heap, - // by default it's only 16MB!...just take care... - RL_DEFAULT_BATCH_BUFFER_ELEMENTS :: 2048 -} - -RL_DEFAULT_BATCH_BUFFERS :: 1 // Default number of batch buffers (multi-buffering) -RL_DEFAULT_BATCH_DRAWCALLS :: 256 // Default number of batch draw calls (by state changes: mode, texture) -RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS :: 4 // Maximum number of additional textures that can be activated on batch drawing (SetShaderValueTexture()) - -// Internal Matrix stack -RL_MAX_MATRIX_STACK_SIZE :: 32 // Maximum size of Matrix stack - -// Shader limits -RL_MAX_SHADER_LOCATIONS :: 32 // Maximum number of shader locations supported - -// Projection matrix culling -RL_CULL_DISTANCE_NEAR :: 0.01 // Default near cull distance -RL_CULL_DISTANCE_FAR :: 1000.0 // Default far cull distance - -// Texture parameters (equivalent to OpenGL defines) -RL_TEXTURE_WRAP_S :: 0x2802 // GL_TEXTURE_WRAP_S -RL_TEXTURE_WRAP_T :: 0x2803 // GL_TEXTURE_WRAP_T -RL_TEXTURE_MAG_FILTER :: 0x2800 // GL_TEXTURE_MAG_FILTER -RL_TEXTURE_MIN_FILTER :: 0x2801 // GL_TEXTURE_MIN_FILTER - -RL_TEXTURE_FILTER_NEAREST :: 0x2600 // GL_NEAREST -RL_TEXTURE_FILTER_LINEAR :: 0x2601 // GL_LINEAR -RL_TEXTURE_FILTER_MIP_NEAREST :: 0x2700 // GL_NEAREST_MIPMAP_NEAREST -RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR :: 0x2702 // GL_NEAREST_MIPMAP_LINEAR -RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST :: 0x2701 // GL_LINEAR_MIPMAP_NEAREST -RL_TEXTURE_FILTER_MIP_LINEAR :: 0x2703 // GL_LINEAR_MIPMAP_LINEAR -RL_TEXTURE_FILTER_ANISOTROPIC :: 0x3000 // Anisotropic filter (custom identifier) - -RL_TEXTURE_WRAP_REPEAT :: 0x2901 // GL_REPEAT -RL_TEXTURE_WRAP_CLAMP :: 0x812F // GL_CLAMP_TO_EDGE -RL_TEXTURE_WRAP_MIRROR_REPEAT :: 0x8370 // GL_MIRRORED_REPEAT -RL_TEXTURE_WRAP_MIRROR_CLAMP :: 0x8742 // GL_MIRROR_CLAMP_EXT - -// Matrix modes (equivalent to OpenGL) -RL_MODELVIEW :: 0x1700 // GL_MODELVIEW -RL_PROJECTION :: 0x1701 // GL_PROJECTION -RL_TEXTURE :: 0x1702 // GL_TEXTURE - -// Primitive assembly draw modes -RL_LINES :: 0x0001 // GL_LINES -RL_TRIANGLES :: 0x0004 // GL_TRIANGLES -RL_QUADS :: 0x0007 // GL_QUADS - -// GL equivalent data types -RL_UNSIGNED_BYTE :: 0x1401 // GL_UNSIGNED_BYTE -RL_FLOAT :: 0x1406 // GL_FLOAT - -// Buffer usage hint -RL_STREAM_DRAW :: 0x88E0 // GL_STREAM_DRAW -RL_STREAM_READ :: 0x88E1 // GL_STREAM_READ -RL_STREAM_COPY :: 0x88E2 // GL_STREAM_COPY -RL_STATIC_DRAW :: 0x88E4 // GL_STATIC_DRAW -RL_STATIC_READ :: 0x88E5 // GL_STATIC_READ -RL_STATIC_COPY :: 0x88E6 // GL_STATIC_COPY -RL_DYNAMIC_DRAW :: 0x88E8 // GL_DYNAMIC_DRAW -RL_DYNAMIC_READ :: 0x88E9 // GL_DYNAMIC_READ -RL_DYNAMIC_COPY :: 0x88EA // GL_DYNAMIC_COPY - -// GL Shader type -RL_FRAGMENT_SHADER :: 0x8B30 // GL_FRAGMENT_SHADER -RL_VERTEX_SHADER :: 0x8B31 // GL_VERTEX_SHADER -RL_COMPUTE_SHADER :: 0x91B9 // GL_COMPUTE_SHADER - -// GL blending factors -RL_ZERO :: 0 // GL_ZERO -RL_ONE :: 1 // GL_ONE -RL_SRC_COLOR :: 0x0300 // GL_SRC_COLOR -RL_ONE_MINUS_SRC_COLOR :: 0x0301 // GL_ONE_MINUS_SRC_COLOR -RL_SRC_ALPHA :: 0x0302 // GL_SRC_ALPHA -RL_ONE_MINUS_SRC_ALPHA :: 0x0303 // GL_ONE_MINUS_SRC_ALPHA -RL_DST_ALPHA :: 0x0304 // GL_DST_ALPHA -RL_ONE_MINUS_DST_ALPHA :: 0x0305 // GL_ONE_MINUS_DST_ALPHA -RL_DST_COLOR :: 0x0306 // GL_DST_COLOR -RL_ONE_MINUS_DST_COLOR :: 0x0307 // GL_ONE_MINUS_DST_COLOR -RL_SRC_ALPHA_SATURATE :: 0x0308 // GL_SRC_ALPHA_SATURATE -RL_CONSTANT_COLOR :: 0x8001 // GL_CONSTANT_COLOR -RL_ONE_MINUS_CONSTANT_COLOR :: 0x8002 // GL_ONE_MINUS_CONSTANT_COLOR -RL_CONSTANT_ALPHA :: 0x8003 // GL_CONSTANT_ALPHA -RL_ONE_MINUS_CONSTANT_ALPHA :: 0x8004 // GL_ONE_MINUS_CONSTANT_ALPHA - -// GL blending functions/equations -RL_FUNC_ADD :: 0x8006 // GL_FUNC_ADD -RL_MIN :: 0x8007 // GL_MIN -RL_MAX :: 0x8008 // GL_MAX -RL_FUNC_SUBTRACT :: 0x800A // GL_FUNC_SUBTRACT -RL_FUNC_REVERSE_SUBTRACT :: 0x800B // GL_FUNC_REVERSE_SUBTRACT -RL_BLEND_EQUATION :: 0x8009 // GL_BLEND_EQUATION -RL_BLEND_EQUATION_RGB :: 0x8009 // GL_BLEND_EQUATION_RGB // (Same as BLEND_EQUATION) -RL_BLEND_EQUATION_ALPHA :: 0x883D // GL_BLEND_EQUATION_ALPHA -RL_BLEND_DST_RGB :: 0x80C8 // GL_BLEND_DST_RGB -RL_BLEND_SRC_RGB :: 0x80C9 // GL_BLEND_SRC_RGB -RL_BLEND_DST_ALPHA :: 0x80CA // GL_BLEND_DST_ALPHA -RL_BLEND_SRC_ALPHA :: 0x80CB // GL_BLEND_SRC_ALPHA -RL_BLEND_COLOR :: 0x8005 // GL_BLEND_COLOR - - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- - - -VertexBufferIndexType :: c.ushort when RL_GRAPHICS_API_OPENGL_ES2 else c.uint - -// Dynamic vertex buffers (position + texcoords + colors + indices arrays) -VertexBuffer :: struct { - elementCount: c.int, // Number of elements in the buffer (QUADS) - - vertices: [^]f32, // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) - texcoords: [^]f32, // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) - colors: [^]u8, // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) - indices: [^]VertexBufferIndexType, // Vertex indices (in case vertex data comes indexed) (6 indices per quad) - vaoId: c.uint, // OpenGL Vertex Array Object id - vboId: [4]c.uint, // OpenGL Vertex Buffer Objects id (4 types of vertex data) -} - -// Draw call type -// NOTE: Only texture changes register a new draw, other state-change-related elements are not -// used at this moment (vaoId, shaderId, matrices), raylib just forces a batch draw call if any -// of those state-change happens (this is done in core module) -DrawCall :: struct { - mode: c.int, // Drawing mode: LINES, TRIANGLES, QUADS - vertexCount: c.int, // Number of vertex of the draw - vertexAlignment: c.int, // Number of vertex required for index alignment (LINES, TRIANGLES) - textureId: c.uint, // Texture id to be used on the draw -> Use to create new draw call if changes -} - -// RenderBatch type -RenderBatch :: struct { - bufferCount: c.int, // Number of vertex buffers (multi-buffering support) - currentBuffer: c.int, // Current buffer tracking in case of multi-buffering - vertexBuffer: [^]VertexBuffer, // Dynamic buffer(s) for vertex data - - draws: [^]DrawCall, // Draw calls array, depends on textureId - drawCounter: c.int, // Draw calls counter - currentDepth: f32, // Current depth value for next draw -} - - -// OpenGL version -GlVersion :: enum c.int { - OPENGL_11 = 1, // OpenGL 1.1 - OPENGL_21, // OpenGL 2.1 (GLSL 120) - OPENGL_33, // OpenGL 3.3 (GLSL 330) - OPENGL_43, // OpenGL 4.3 (using GLSL 330) - OPENGL_ES_20, // OpenGL ES 2.0 (GLSL 100) - OPENGL_ES_30, // OpenGL ES 3.0 (GLSL 300 es) -} - - -// Shader attribute data types -ShaderAttributeDataType :: enum c.int { - FLOAT = 0, // Shader attribute type: float - VEC2, // Shader attribute type: vec2 (2 float) - VEC3, // Shader attribute type: vec3 (3 float) - VEC4, // Shader attribute type: vec4 (4 float) -} - -// Framebuffer attachment type -// NOTE: By default up to 8 color channels defined, but it can be more -FramebufferAttachType :: enum c.int { - COLOR_CHANNEL0 = 0, // Framebuffer attachment type: color 0 - COLOR_CHANNEL1 = 1, // Framebuffer attachment type: color 1 - COLOR_CHANNEL2 = 2, // Framebuffer attachment type: color 2 - COLOR_CHANNEL3 = 3, // Framebuffer attachment type: color 3 - COLOR_CHANNEL4 = 4, // Framebuffer attachment type: color 4 - COLOR_CHANNEL5 = 5, // Framebuffer attachment type: color 5 - COLOR_CHANNEL6 = 6, // Framebuffer attachment type: color 6 - COLOR_CHANNEL7 = 7, // Framebuffer attachment type: color 7 - DEPTH = 100, // Framebuffer attachment type: depth - STENCIL = 200, // Framebuffer attachment type: stencil -} - -// Framebuffer texture attachment type -FramebufferAttachTextureType :: enum c.int { - CUBEMAP_POSITIVE_X = 0, // Framebuffer texture attachment type: cubemap, +X side - CUBEMAP_NEGATIVE_X = 1, // Framebuffer texture attachment type: cubemap, -X side - CUBEMAP_POSITIVE_Y = 2, // Framebuffer texture attachment type: cubemap, +Y side - CUBEMAP_NEGATIVE_Y = 3, // Framebuffer texture attachment type: cubemap, -Y side - CUBEMAP_POSITIVE_Z = 4, // Framebuffer texture attachment type: cubemap, +Z side - CUBEMAP_NEGATIVE_Z = 5, // Framebuffer texture attachment type: cubemap, -Z side - TEXTURE2D = 100, // Framebuffer texture attachment type: texture2d - RENDERBUFFER = 200, // Framebuffer texture attachment type: renderbuffer -} - -CullMode :: enum c.int { - FRONT = 0, - BACK, -} - -@(default_calling_convention="c") -foreign lib { - //------------------------------------------------------------------------------------ - // Functions Declaration - Matrix operations - //------------------------------------------------------------------------------------ - rlMatrixMode :: proc(mode: c.int) --- // Choose the current matrix to be transformed - rlPushMatrix :: proc() --- // Push the current matrix to stack - rlPopMatrix :: proc() --- // Pop lattest inserted matrix from stack - rlLoadIdentity :: proc() --- // Reset current matrix to identity matrix - rlTranslatef :: proc(x, y, z: f32) --- // Multiply the current matrix by a translation matrix - rlRotatef :: proc(angleDeg: f32, x, y, z: f32) --- // Multiply the current matrix by a rotation matrix - rlScalef :: proc(x, y, z: f32) --- // Multiply the current matrix by a scaling matrix - rlMultMatrixf :: proc(matf: [^]f32) --- // Multiply the current matrix by another matrix - rlFrustum :: proc(left, right, bottom, top, znear, zfar: f64) --- - rlOrtho :: proc(left, right, bottom, top, znear, zfar: f64) --- - rlViewport :: proc(x, y, width, height: c.int) --- // Set the viewport area - - //------------------------------------------------------------------------------------ - // Functions Declaration - Vertex level operations - //------------------------------------------------------------------------------------ - rlBegin :: proc(mode: c.int) --- // Initialize drawing mode (how to organize vertex) - rlEnd :: proc() --- // Finish vertex providing - rlVertex2i :: proc(x, y: c.int) --- // Define one vertex (position) - 2 int - rlVertex2f :: proc(x, y: f32) --- // Define one vertex (position) - 2 f32 - rlVertex3f :: proc(x, y, z: f32) --- // Define one vertex (position) - 3 f32 - rlTexCoord2f :: proc(x, y: f32) --- // Define one vertex (texture coordinate) - 2 f32 - rlNormal3f :: proc(x, y, z: f32) --- // Define one vertex (normal) - 3 f32 - rlColor4ub :: proc(r, g, b, a: u8) --- // Define one vertex (color) - 4 byte - rlColor3f :: proc(x, y, z: f32) --- // Define one vertex (color) - 3 f32 - rlColor4f :: proc(x, y, z, w: f32) --- // Define one vertex (color) - 4 f32 - - //------------------------------------------------------------------------------------ - // Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) - // NOTE: This functions are used to completely abstract raylib code from OpenGL layer, - // some of them are direct wrappers over OpenGL calls, some others are custom - //------------------------------------------------------------------------------------ - - // Vertex buffers state - rlEnableVertexArray :: proc(vaoId: c.uint) -> bool --- // Enable vertex array (VAO, if supported) - rlDisableVertexArray :: proc() --- // Disable vertex array (VAO, if supported) - rlEnableVertexBuffer :: proc(id: c.uint) --- // Enable vertex buffer (VBO) - rlDisableVertexBuffer :: proc() --- // Disable vertex buffer (VBO) - rlEnableVertexBufferElement :: proc(id: c.uint) --- // Enable vertex buffer element (VBO element) - rlDisableVertexBufferElement :: proc() --- // Disable vertex buffer element (VBO element) - rlEnableVertexAttribute :: proc(index: c.uint) --- // Enable vertex attribute index - rlDisableVertexAttribute :: proc(index: c.uint) --- // Disable vertex attribute index - when RL_GRAPHICS_API_OPENGL_11 { - rlEnableStatePointer :: proc(vertexAttribType: c.int, buffer: rawptr) --- - rlDisableStatePointer :: proc(vertexAttribType: c.int) --- - } - - // Textures state - rlActiveTextureSlot :: proc(slot: c.int) --- // Select and active a texture slot - rlEnableTexture :: proc(id: c.uint) --- // Enable texture - rlDisableTexture :: proc() --- // Disable texture - rlEnableTextureCubemap :: proc(id: c.uint) --- // Enable texture cubemap - rlDisableTextureCubemap :: proc() --- // Disable texture cubemap - rlTextureParameters :: proc(id: c.uint, param: c.int, value: c.int) --- // Set texture parameters (filter, wrap) - rlCubemapParameters :: proc(id: i32, param: c.int, value: c.int) --- // Set cubemap parameters (filter, wrap) - - // Shader state - rlEnableShader :: proc(id: c.uint) --- // Enable shader program - rlDisableShader :: proc() --- // Disable shader program - - // Framebuffer state - rlEnableFramebuffer :: proc(id: c.uint) --- // Enable render texture (fbo) - rlDisableFramebuffer :: proc() --- // Disable render texture (fbo), return to default framebuffer - rlActiveDrawBuffers :: proc(count: c.int) --- // Activate multiple draw color buffers - rlBlitFramebuffer :: proc(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, bufferMask: c.int) --- // Blit active framebuffer to main framebuffer - - // General render state - rlDisableColorBlend :: proc() --- // Disable color blending - rlEnableDepthTest :: proc() --- // Enable depth test - rlDisableDepthTest :: proc() --- // Disable depth test - rlEnableDepthMask :: proc() --- // Enable depth write - rlDisableDepthMask :: proc() --- // Disable depth write - rlEnableBackfaceCulling :: proc() --- // Enable backface culling - rlDisableBackfaceCulling :: proc() --- // Disable backface culling - rlSetCullFace :: proc(mode: CullMode) --- // Set face culling mode - rlEnableScissorTest :: proc() --- // Enable scissor test - rlDisableScissorTest :: proc() --- // Disable scissor test - rlScissor :: proc(x, y, width, height: c.int) --- // Scissor test - rlEnableWireMode :: proc() --- // Enable wire mode - rlEnablePointMode :: proc() --- // Enable point mode - rlDisableWireMode :: proc() --- // Disable wire and point modes - rlSetLineWidth :: proc(width: f32) --- // Set the line drawing width - rlGetLineWidth :: proc() -> f32 --- // Get the line drawing width - rlEnableSmoothLines :: proc() --- // Enable line aliasing - rlDisableSmoothLines :: proc() --- // Disable line aliasing - rlEnableStereoRender :: proc() --- // Enable stereo rendering - rlDisableStereoRender :: proc() --- // Disable stereo rendering - rlIsStereoRenderEnabled :: proc() -> bool --- // Check if stereo render is enabled - - - rlClearColor :: proc(r, g, b, a: u8) --- // Clear color buffer with color - rlClearScreenBuffers :: proc() --- // Clear used screen buffers (color and depth) - rlCheckErrors :: proc() --- // Check and log OpenGL error codes - rlSetBlendMode :: proc(mode: c.int) --- // Set blending mode - rlSetBlendFactors :: proc(glSrcFactor, glDstFactor, glEquation: c.int) --- // Set blending mode factor and equation (using OpenGL factors) - rlSetBlendFactorsSeparate :: proc(glSrcRGB, glDstRGB, glSrcAlpha, glDstAlpha, glEqRGB, glEqAlpha: c.int) --- // Set blending mode factors and equations separately (using OpenGL factors) - - //------------------------------------------------------------------------------------ - // Functions Declaration - rlgl functionality - //------------------------------------------------------------------------------------ - // rlgl initialization functions - rlglInit :: proc(width, height: c.int) --- // Initialize rlgl (buffers, shaders, textures, states) - rlglClose :: proc() --- // De-initialize rlgl (buffers, shaders, textures) - rlLoadExtensions :: proc(loader: rawptr) --- // Load OpenGL extensions (loader function required) - rlGetVersion :: proc() -> GlVersion --- // Get current OpenGL version - rlSetFramebufferWidth :: proc(width: c.int) --- // Set current framebuffer width - rlGetFramebufferWidth :: proc() -> c.int --- // Get default framebuffer width - rlSetFramebufferHeight :: proc(height: c.int) --- // Set current framebuffer height - rlGetFramebufferHeight :: proc() -> c.int --- // Get default framebuffer height - - - rlGetTextureIdDefault :: proc() -> c.uint --- // Get default texture id - rlGetShaderIdDefault :: proc() -> c.uint --- // Get default shader id - rlGetShaderLocsDefault :: proc() -> [^]c.int --- // Get default shader locations - - // Render batch management - // NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode - // but this render batch API is exposed in case of custom batches are required - rlLoadRenderBatch :: proc(numBuffers, bufferElements: c.int) -> RenderBatch --- // Load a render batch system - rlUnloadRenderBatch :: proc(batch: RenderBatch) --- // Unload render batch system - rlDrawRenderBatch :: proc(batch: ^RenderBatch) --- // Draw render batch data (Update->Draw->Reset) - rlSetRenderBatchActive :: proc(batch: ^RenderBatch) --- // Set the active render batch for rlgl (NULL for default internal) - rlDrawRenderBatchActive :: proc() --- // Update and draw internal render batch - rlCheckRenderBatchLimit :: proc(vCount: c.int) -> c.int --- // Check internal buffer overflow for a given number of vertex - - rlSetTexture :: proc(id: c.uint) --- // Set current texture for render batch and check buffers limits - - //------------------------------------------------------------------------------------------------------------------------ - - // Vertex buffers management - rlLoadVertexArray :: proc() -> c.uint --- // Load vertex array (vao) if supported - rlLoadVertexBuffer :: proc(buffer: rawptr, size: c.int, is_dynamic: bool) -> c.uint --- // Load a vertex buffer attribute - rlLoadVertexBufferElement :: proc(buffer: rawptr, size: c.int, is_dynamic: bool) -> c.uint --- // Load a new attributes element buffer - rlUpdateVertexBuffer :: proc(bufferId: c.uint, data: rawptr, dataSize: c.int, offset: c.int) --- // Update GPU buffer with new data - rlUpdateVertexBufferElements :: proc(id: c.uint, data: rawptr, dataSize: c.int, offset: c.int) --- // Update vertex buffer elements with new data - rlUnloadVertexArray :: proc(vaoId: c.uint) --- - rlUnloadVertexBuffer :: proc(vboId: c.uint) --- - rlSetVertexAttribute :: proc(index: c.uint, compSize: c.int, type: c.int, normalized: bool, stride: c.int, pointer: rawptr) --- - rlSetVertexAttributeDivisor :: proc(index: c.uint, divisor: c.int) --- - rlSetVertexAttributeDefault :: proc(locIndex: c.int, value: rawptr, attribType: c.int, count: c.int) --- // Set vertex attribute default value - rlDrawVertexArray :: proc(offset: c.int, count: c.int) --- - rlDrawVertexArrayElements :: proc(offset: c.int, count: c.int, buffer: rawptr) --- - rlDrawVertexArrayInstanced :: proc(offset: c.int, count: c.int, instances: c.int) --- - rlDrawVertexArrayElementsInstanced :: proc(offset: c.int, count: c.int, buffer: rawptr, instances: c.int) --- - - // Textures management - rlLoadTexture :: proc(data: rawptr, width, height: c.int, format: c.int, mipmapCount: c.int) -> c.uint --- // Load texture in GPU - rlLoadTextureDepth :: proc(width, height: c.int, useRenderBuffer: bool) -> c.uint --- // Load depth texture/renderbuffer (to be attached to fbo) - rlLoadTextureCubemap :: proc(data: rawptr, size: c.int, format: c.int) -> c.uint --- // Load texture cubemap - rlUpdateTexture :: proc(id: c.uint, offsetX, offsetY: c.int, width, height: c.int, format: c.int, data: rawptr) --- // Update GPU texture with new data - rlGetGlTextureFormats :: proc(format: c.int, glInternalFormat, glFormat, glType: ^c.uint) --- // Get OpenGL internal formats - rlGetPixelFormatName :: proc(format: c.uint) -> cstring --- // Get name string for pixel format - rlUnloadTexture :: proc(id: c.uint) --- // Unload texture from GPU memory - rlGenTextureMipmaps :: proc(id: c.uint, width, height: c.int, format: c.int, mipmaps: ^c.int) --- // Generate mipmap data for selected texture - rlReadTexturePixels :: proc(id: c.uint, width, height: c.int, format: c.int) -> rawptr --- // Read texture pixel data - rlReadScreenPixels :: proc(width, height: c.int) -> [^]byte --- // Read screen pixel data (color buffer) - - // Framebuffer management (fbo) - rlLoadFramebuffer :: proc(width, height: c.int) -> c.uint --- // Load an empty framebuffer - rlFramebufferAttach :: proc(fboId, texId: c.uint, attachType: c.int, texType: c.int, mipLevel: c.int) --- // Attach texture/renderbuffer to a framebuffer - rlFramebufferComplete :: proc(id: c.uint) -> bool --- // Verify framebuffer is complete - rlUnloadFramebuffer :: proc(id: c.uint) --- // Delete framebuffer from GPU - - // Shaders management - rlLoadShaderCode :: proc(vsCode, fsCode: cstring) -> c.uint --- // Load shader from code strings - rlCompileShader :: proc(shaderCode: cstring, type: c.int) -> c.uint --- // Compile custom shader and return shader id (type: RL_VERTEX_SHADER, RL_FRAGMENT_SHADER, RL_COMPUTE_SHADER) - rlLoadShaderProgram :: proc(vShaderId, fShaderId: c.uint) -> c.uint --- // Load custom shader program - rlUnloadShaderProgram :: proc(id: c.uint) --- // Unload shader program - rlGetLocationUniform :: proc(shaderId: c.uint, uniformName: cstring) -> c.int --- // Get shader location uniform - rlGetLocationAttrib :: proc(shaderId: c.uint, attribName: cstring) -> c.int --- // Get shader location attribute - rlSetUniform :: proc(locIndex: c.int, value: rawptr, uniformType: c.int, count: c.int) --- // Set shader value uniform - rlSetUniformMatrix :: proc(locIndex: c.int, mat: Matrix) --- // Set shader value matrix - rlSetUniformSampler :: proc(locIndex: c.int, textureId: c.uint) --- // Set shader value sampler - rlSetShader :: proc(id: c.uint, locs: [^]c.int) --- // Set shader currently active (id and locations) - - // Compute shader management - rlLoadComputeShaderProgram :: proc(shaderId: c.uint) -> c.uint --- // Load compute shader program - rlComputeShaderDispatch :: proc(groupX, groupY, groupZ: c.uint) --- // Dispatch compute shader (equivalent to *draw* for graphics pipeline) - - // Shader buffer storage object management (ssbo) - rlLoadShaderBuffer :: proc(size: c.uint, data: rawptr, usageHint: c.int) -> c.uint --- // Load shader storage buffer object (SSBO) - rlUnloadShaderBuffer :: proc(ssboId: c.uint) --- // Unload shader storage buffer object (SSBO) - rlUpdateShaderBuffer :: proc(id: c.uint, data: rawptr, dataSize: c.uint, offset: c.uint) --- // Update SSBO buffer data - rlBindShaderBuffer :: proc(id: c.uint, index: c.uint) --- // Bind SSBO buffer - rlReadShaderBuffer :: proc(id: c.uint, dest: rawptr, count: c.uint, offset: c.uint) --- // Read SSBO buffer data (GPU->CPU) - rlCopyShaderBuffer :: proc(destId, srcId: c.uint, destOffset, srcOffset: c.uint, count: c.uint) --- // Copy SSBO data between buffers - rlGetShaderBufferSize :: proc(id: c.uint) -> c.uint --- // Get SSBO buffer size - - // Buffer management - rlBindImageTexture :: proc(id: c.uint, index: c.uint, format: c.int, readonly: bool) --- // Bind image texture - - // Matrix state management - rlGetMatrixModelview :: proc() -> Matrix --- // Get internal modelview matrix - rlGetMatrixProjection :: proc() -> Matrix --- // Get internal projection matrix - rlGetMatrixTransform :: proc() -> Matrix --- // Get internal accumulated transform matrix - rlGetMatrixProjectionStereo :: proc(eye: c.int) -> Matrix --- // Get internal projection matrix for stereo render (selected eye) - rlGetMatrixViewOffsetStereo :: proc(eye: c.int) -> Matrix --- // Get internal view offset matrix for stereo render (selected eye) - rlSetMatrixProjection :: proc(proj: Matrix) --- // Set a custom projection matrix (replaces internal projection matrix) - rlSetMatrixModelview :: proc(view: Matrix) --- // Set a custom modelview matrix (replaces internal modelview matrix) - rlSetMatrixProjectionStereo :: proc(right, left: Matrix) --- // Set eyes projection matrices for stereo rendering - rlSetMatrixViewOffsetStereo :: proc(right, left: Matrix) --- // Set eyes view offsets matrices for stereo rendering - - // Quick and dirty cube/quad buffers load->draw->unload - rlLoadDrawCube :: proc() --- // Load and draw a cube - rlLoadDrawQuad :: proc() --- // Load and draw a quad -} diff --git a/vendor/raylib/rlgl/rlgl.odin b/vendor/raylib/rlgl/rlgl.odin new file mode 100644 index 000000000..cef31c238 --- /dev/null +++ b/vendor/raylib/rlgl/rlgl.odin @@ -0,0 +1,581 @@ +/********************************************************************************************** +* +* rlgl v5.0 - A multi-OpenGL abstraction layer with an immediate-mode style API +* +* DESCRIPTION: +* An abstraction layer for multiple OpenGL versions (1.1, 2.1, 3.3 Core, 4.3 Core, ES 2.0) +* that provides a pseudo-OpenGL 1.1 immediate-mode style API (rlVertex, rlTranslate, rlRotate...) +* +* ADDITIONAL NOTES: +* When choosing an OpenGL backend different than OpenGL 1.1, some internal buffer are +* initialized on rlglInit() to accumulate vertex data. +* +* When an internal state change is required all the stored vertex data is renderer in batch, +* additionally, rlDrawRenderBatchActive() could be called to force flushing of the batch. +* +* Some resources are also loaded for convenience, here the complete list: +* - Default batch (RLGL.defaultBatch): RenderBatch system to accumulate vertex data +* - Default texture (RLGL.defaultTextureId): 1x1 white pixel R8G8B8A8 +* - Default shader (RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs) +* +* Internal buffer (and resources) must be manually unloaded calling rlglClose(). +* +* CONFIGURATION: +* #define GRAPHICS_API_OPENGL_11 +* #define GRAPHICS_API_OPENGL_21 +* #define GRAPHICS_API_OPENGL_33 +* #define GRAPHICS_API_OPENGL_43 +* #define GRAPHICS_API_OPENGL_ES2 +* #define GRAPHICS_API_OPENGL_ES3 +* Use selected OpenGL graphics backend, should be supported by platform +* Those preprocessor defines are only used on rlgl module, if OpenGL version is +* required by any other module, use rlGetVersion() to check it +* +* #define RLGL_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RLGL_RENDER_TEXTURES_HINT +* Enable framebuffer objects (fbo) support (enabled by default) +* Some GPUs could not support them despite the OpenGL version +* +* #define RLGL_SHOW_GL_DETAILS_INFO +* Show OpenGL extensions and capabilities detailed logs on init +* +* #define RLGL_ENABLE_OPENGL_DEBUG_CONTEXT +* Enable debug context (only available on OpenGL 4.3) +* +* rlgl capabilities could be customized just defining some internal +* values before library inclusion (default values listed): +* +* #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 // Default internal render batch elements limits +* #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +* #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) +* #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) +* +* #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack +* #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +* #define RL_CULL_DISTANCE_NEAR 0.01 // Default projection matrix near cull distance +* #define RL_CULL_DISTANCE_FAR 1000.0 // Default projection matrix far cull distance +* +* When loading a shader, the following vertex attributes and uniform +* location names are tried to be set automatically: +* +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: 0 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: 1 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: 2 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: 3 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: 4 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: 5 +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView)) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) +* +* DEPENDENCIES: +* - OpenGL libraries (depending on platform and OpenGL version selected) +* - GLAD OpenGL extensions loading library (only for OpenGL 3.3 Core, 4.3 Core) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2023 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + + +package rlgl + +import "core:c" +import rl "../." + +VERSION :: "5.0" + +RAYLIB_SHARED :: #config(RAYLIB_SHARED, false) + +// Note: We pull in the full raylib library. If you want a truly stand-alone rlgl, then: +// - Compile a separate rlgl library and use that in the foreign import blocks below. +// - Remove the `import rl "../."` line +// - Copy the code from raylib.odin for any types we alias from that package (see PixelFormat etc) + +when ODIN_OS == .Windows { + @(extra_linker_flags="/NODEFAULTLIB:" + ("msvcrt" when RAYLIB_SHARED else "libcmt")) + foreign import lib { + "../windows/raylibdll.lib" when RAYLIB_SHARED else "../windows/raylib.lib" , + "system:Winmm.lib", + "system:Gdi32.lib", + "system:User32.lib", + "system:Shell32.lib", + } +} else when ODIN_OS == .Linux { + foreign import lib { + // Note(bumbread): I'm not sure why in `linux/` folder there are + // multiple copies of raylib.so, but since these bindings are for + // particular version of the library, I better specify it. Ideally, + // though, it's best specified in terms of major (.so.4) + "../linux/libraylib.so.500" when RAYLIB_SHARED else "../linux/libraylib.a", + "system:dl", + "system:pthread", + } +} else when ODIN_OS == .Darwin { + foreign import lib { + "../macos" + + ("-arm64" when ODIN_ARCH == .arm64 else "") + + "/libraylib" + (".500.dylib" when RAYLIB_SHARED else ".a"), + "system:Cocoa.framework", + "system:OpenGL.framework", + "system:IOKit.framework", + } +} else { + foreign import lib "system:raylib" +} + +GRAPHICS_API_OPENGL_11 :: false +GRAPHICS_API_OPENGL_21 :: true +GRAPHICS_API_OPENGL_33 :: GRAPHICS_API_OPENGL_21 // default currently +GRAPHICS_API_OPENGL_ES2 :: false +GRAPHICS_API_OPENGL_43 :: false +GRAPHICS_API_OPENGL_ES3 :: false + +when GRAPHICS_API_OPENGL_ES3 { + GRAPHICS_API_OPENGL_ES2 :: true +} + +when !GRAPHICS_API_OPENGL_ES2 { + // This is the maximum amount of elements (quads) per batch + // NOTE: Be careful with text, every letter maps to a quad + DEFAULT_BATCH_BUFFER_ELEMENTS :: 8192 +} else { + // We reduce memory sizes for embedded systems (RPI and HTML5) + // NOTE: On HTML5 (emscripten) this is allocated on heap, + // by default it's only 16MB!...just take care... + DEFAULT_BATCH_BUFFER_ELEMENTS :: 2048 +} + +DEFAULT_BATCH_BUFFERS :: 1 // Default number of batch buffers (multi-buffering) +DEFAULT_BATCH_DRAWCALLS :: 256 // Default number of batch draw calls (by state changes: mode, texture) +DEFAULT_BATCH_MAX_TEXTURE_UNITS :: 4 // Maximum number of additional textures that can be activated on batch drawing (SetShaderValueTexture()) + +// Internal Matrix stack +MAX_MATRIX_STACK_SIZE :: 32 // Maximum size of Matrix stack + +// Shader limits +MAX_SHADER_LOCATIONS :: 32 // Maximum number of shader locations supported + +// Projection matrix culling +CULL_DISTANCE_NEAR :: 0.01 // Default near cull distance +CULL_DISTANCE_FAR :: 1000.0 // Default far cull distance + +// Texture parameters (equivalent to OpenGL defines) +TEXTURE_WRAP_S :: 0x2802 // GL_TEXTURE_WRAP_S +TEXTURE_WRAP_T :: 0x2803 // GL_TEXTURE_WRAP_T +TEXTURE_MAG_FILTER :: 0x2800 // GL_TEXTURE_MAG_FILTER +TEXTURE_MIN_FILTER :: 0x2801 // GL_TEXTURE_MIN_FILTER + +TEXTURE_FILTER_NEAREST :: 0x2600 // GL_NEAREST +TEXTURE_FILTER_LINEAR :: 0x2601 // GL_LINEAR +TEXTURE_FILTER_MIP_NEAREST :: 0x2700 // GL_NEAREST_MIPMAP_NEAREST +TEXTURE_FILTER_NEAREST_MIP_LINEAR :: 0x2702 // GL_NEAREST_MIPMAP_LINEAR +TEXTURE_FILTER_LINEAR_MIP_NEAREST :: 0x2701 // GL_LINEAR_MIPMAP_NEAREST +TEXTURE_FILTER_MIP_LINEAR :: 0x2703 // GL_LINEAR_MIPMAP_LINEAR +TEXTURE_FILTER_ANISOTROPIC :: 0x3000 // Anisotropic filter (custom identifier) + +TEXTURE_WRAP_REPEAT :: 0x2901 // GL_REPEAT +TEXTURE_WRAP_CLAMP :: 0x812F // GL_CLAMP_TO_EDGE +TEXTURE_WRAP_MIRROR_REPEAT :: 0x8370 // GL_MIRRORED_REPEAT +TEXTURE_WRAP_MIRROR_CLAMP :: 0x8742 // GL_MIRROR_CLAMP_EXT + +// Matrix modes (equivalent to OpenGL) +MODELVIEW :: 0x1700 // GL_MODELVIEW +PROJECTION :: 0x1701 // GL_PROJECTION +TEXTURE :: 0x1702 // GL_TEXTURE + +// Primitive assembly draw modes +LINES :: 0x0001 // GL_LINES +TRIANGLES :: 0x0004 // GL_TRIANGLES +QUADS :: 0x0007 // GL_QUADS + +// GL equivalent data types +UNSIGNED_BYTE :: 0x1401 // GL_UNSIGNED_BYTE +FLOAT :: 0x1406 // GL_FLOAT + +// Buffer usage hint +STREAM_DRAW :: 0x88E0 // GL_STREAM_DRAW +STREAM_READ :: 0x88E1 // GL_STREAM_READ +STREAM_COPY :: 0x88E2 // GL_STREAM_COPY +STATIC_DRAW :: 0x88E4 // GL_STATIC_DRAW +STATIC_READ :: 0x88E5 // GL_STATIC_READ +STATIC_COPY :: 0x88E6 // GL_STATIC_COPY +DYNAMIC_DRAW :: 0x88E8 // GL_DYNAMIC_DRAW +DYNAMIC_READ :: 0x88E9 // GL_DYNAMIC_READ +DYNAMIC_COPY :: 0x88EA // GL_DYNAMIC_COPY + +// GL Shader type +FRAGMENT_SHADER :: 0x8B30 // GL_FRAGMENT_SHADER +VERTEX_SHADER :: 0x8B31 // GL_VERTEX_SHADER +COMPUTE_SHADER :: 0x91B9 // GL_COMPUTE_SHADER + +// GL blending factors +ZERO :: 0 // GL_ZERO +ONE :: 1 // GL_ONE +SRC_COLOR :: 0x0300 // GL_SRC_COLOR +ONE_MINUS_SRC_COLOR :: 0x0301 // GL_ONE_MINUS_SRC_COLOR +SRC_ALPHA :: 0x0302 // GL_SRC_ALPHA +ONE_MINUS_SRC_ALPHA :: 0x0303 // GL_ONE_MINUS_SRC_ALPHA +DST_ALPHA :: 0x0304 // GL_DST_ALPHA +ONE_MINUS_DST_ALPHA :: 0x0305 // GL_ONE_MINUS_DST_ALPHA +DST_COLOR :: 0x0306 // GL_DST_COLOR +ONE_MINUS_DST_COLOR :: 0x0307 // GL_ONE_MINUS_DST_COLOR +SRC_ALPHA_SATURATE :: 0x0308 // GL_SRC_ALPHA_SATURATE +CONSTANT_COLOR :: 0x8001 // GL_CONSTANT_COLOR +ONE_MINUS_CONSTANT_COLOR :: 0x8002 // GL_ONE_MINUS_CONSTANT_COLOR +CONSTANT_ALPHA :: 0x8003 // GL_CONSTANT_ALPHA +ONE_MINUS_CONSTANT_ALPHA :: 0x8004 // GL_ONE_MINUS_CONSTANT_ALPHA + +// GL blending functions/equations +FUNC_ADD :: 0x8006 // GL_FUNC_ADD +MIN :: 0x8007 // GL_MIN +MAX :: 0x8008 // GL_MAX +FUNC_SUBTRACT :: 0x800A // GL_FUNC_SUBTRACT +FUNC_REVERSE_SUBTRACT :: 0x800B // GL_FUNC_REVERSE_SUBTRACT +BLEND_EQUATION :: 0x8009 // GL_BLEND_EQUATION +BLEND_EQUATION_RGB :: 0x8009 // GL_BLEND_EQUATION_RGB // (Same as BLEND_EQUATION) +BLEND_EQUATION_ALPHA :: 0x883D // GL_BLEND_EQUATION_ALPHA +BLEND_DST_RGB :: 0x80C8 // GL_BLEND_DST_RGB +BLEND_SRC_RGB :: 0x80C9 // GL_BLEND_SRC_RGB +BLEND_DST_ALPHA :: 0x80CA // GL_BLEND_DST_ALPHA +BLEND_SRC_ALPHA :: 0x80CB // GL_BLEND_SRC_ALPHA +BLEND_COLOR :: 0x8005 // GL_BLEND_COLOR + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + + +VertexBufferIndexType :: c.ushort when GRAPHICS_API_OPENGL_ES2 else c.uint + +// Dynamic vertex buffers (position + texcoords + colors + indices arrays) +VertexBuffer :: struct { + elementCount: c.int, // Number of elements in the buffer (QUADS) + + vertices: [^]f32, // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + texcoords: [^]f32, // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + colors: [^]u8, // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) + indices: [^]VertexBufferIndexType, // Vertex indices (in case vertex data comes indexed) (6 indices per quad) + vaoId: c.uint, // OpenGL Vertex Array Object id + vboId: [4]c.uint, // OpenGL Vertex Buffer Objects id (4 types of vertex data) +} + +// Draw call type +// NOTE: Only texture changes register a new draw, other state-change-related elements are not +// used at this moment (vaoId, shaderId, matrices), raylib just forces a batch draw call if any +// of those state-change happens (this is done in core module) +DrawCall :: struct { + mode: c.int, // Drawing mode: LINES, TRIANGLES, QUADS + vertexCount: c.int, // Number of vertex of the draw + vertexAlignment: c.int, // Number of vertex required for index alignment (LINES, TRIANGLES) + textureId: c.uint, // Texture id to be used on the draw -> Use to create new draw call if changes +} + +// RenderBatch type +RenderBatch :: struct { + bufferCount: c.int, // Number of vertex buffers (multi-buffering support) + currentBuffer: c.int, // Current buffer tracking in case of multi-buffering + vertexBuffer: [^]VertexBuffer, // Dynamic buffer(s) for vertex data + + draws: [^]DrawCall, // Draw calls array, depends on textureId + drawCounter: c.int, // Draw calls counter + currentDepth: f32, // Current depth value for next draw +} + +// OpenGL version +GlVersion :: enum c.int { + OPENGL_11 = 1, // OpenGL 1.1 + OPENGL_21, // OpenGL 2.1 (GLSL 120) + OPENGL_33, // OpenGL 3.3 (GLSL 330) + OPENGL_43, // OpenGL 4.3 (using GLSL 330) + OPENGL_ES_20, // OpenGL ES 2.0 (GLSL 100) + OPENGL_ES_30, // OpenGL ES 3.0 (GLSL 300 es) +} + +PixelFormat :: rl.PixelFormat +TextureFilter :: rl.TextureFilter +BlendMode :: rl.BlendMode +ShaderLocationIndex :: rl.ShaderLocationIndex +ShaderUniformDataType :: rl.ShaderUniformDataType + +// Shader attribute data types +ShaderAttributeDataType :: enum c.int { + FLOAT = 0, // Shader attribute type: float + VEC2, // Shader attribute type: vec2 (2 float) + VEC3, // Shader attribute type: vec3 (3 float) + VEC4, // Shader attribute type: vec4 (4 float) +} + +// Framebuffer attachment type +// NOTE: By default up to 8 color channels defined, but it can be more +FramebufferAttachType :: enum c.int { + COLOR_CHANNEL0 = 0, // Framebuffer attachment type: color 0 + COLOR_CHANNEL1 = 1, // Framebuffer attachment type: color 1 + COLOR_CHANNEL2 = 2, // Framebuffer attachment type: color 2 + COLOR_CHANNEL3 = 3, // Framebuffer attachment type: color 3 + COLOR_CHANNEL4 = 4, // Framebuffer attachment type: color 4 + COLOR_CHANNEL5 = 5, // Framebuffer attachment type: color 5 + COLOR_CHANNEL6 = 6, // Framebuffer attachment type: color 6 + COLOR_CHANNEL7 = 7, // Framebuffer attachment type: color 7 + DEPTH = 100, // Framebuffer attachment type: depth + STENCIL = 200, // Framebuffer attachment type: stencil +} + +// Framebuffer texture attachment type +FramebufferAttachTextureType :: enum c.int { + CUBEMAP_POSITIVE_X = 0, // Framebuffer texture attachment type: cubemap, +X side + CUBEMAP_NEGATIVE_X = 1, // Framebuffer texture attachment type: cubemap, -X side + CUBEMAP_POSITIVE_Y = 2, // Framebuffer texture attachment type: cubemap, +Y side + CUBEMAP_NEGATIVE_Y = 3, // Framebuffer texture attachment type: cubemap, -Y side + CUBEMAP_POSITIVE_Z = 4, // Framebuffer texture attachment type: cubemap, +Z side + CUBEMAP_NEGATIVE_Z = 5, // Framebuffer texture attachment type: cubemap, -Z side + TEXTURE2D = 100, // Framebuffer texture attachment type: texture2d + RENDERBUFFER = 200, // Framebuffer texture attachment type: renderbuffer +} + +CullMode :: enum c.int { + FRONT = 0, + BACK, +} + +Matrix :: rl.Matrix + +@(default_calling_convention="c", link_prefix="rl") +foreign lib { + //------------------------------------------------------------------------------------ + // Functions Declaration - Matrix operations + //------------------------------------------------------------------------------------ + MatrixMode :: proc(mode: c.int) --- // Choose the current matrix to be transformed + PushMatrix :: proc() --- // Push the current matrix to stack + PopMatrix :: proc() --- // Pop lattest inserted matrix from stack + LoadIdentity :: proc() --- // Reset current matrix to identity matrix + Translatef :: proc(x, y, z: f32) --- // Multiply the current matrix by a translation matrix + Rotatef :: proc(angleDeg: f32, x, y, z: f32) --- // Multiply the current matrix by a rotation matrix + Scalef :: proc(x, y, z: f32) --- // Multiply the current matrix by a scaling matrix + MultMatrixf :: proc(matf: [^]f32) --- // Multiply the current matrix by another matrix + Frustum :: proc(left, right, bottom, top, znear, zfar: f64) --- + Ortho :: proc(left, right, bottom, top, znear, zfar: f64) --- + Viewport :: proc(x, y, width, height: c.int) --- // Set the viewport area + + //------------------------------------------------------------------------------------ + // Functions Declaration - Vertex level operations + //------------------------------------------------------------------------------------ + Begin :: proc(mode: c.int) --- // Initialize drawing mode (how to organize vertex) + End :: proc() --- // Finish vertex providing + Vertex2i :: proc(x, y: c.int) --- // Define one vertex (position) - 2 int + Vertex2f :: proc(x, y: f32) --- // Define one vertex (position) - 2 f32 + Vertex3f :: proc(x, y, z: f32) --- // Define one vertex (position) - 3 f32 + TexCoord2f :: proc(x, y: f32) --- // Define one vertex (texture coordinate) - 2 f32 + Normal3f :: proc(x, y, z: f32) --- // Define one vertex (normal) - 3 f32 + Color4ub :: proc(r, g, b, a: u8) --- // Define one vertex (color) - 4 byte + Color3f :: proc(x, y, z: f32) --- // Define one vertex (color) - 3 f32 + Color4f :: proc(x, y, z, w: f32) --- // Define one vertex (color) - 4 f32 + + //------------------------------------------------------------------------------------ + // Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) + // NOTE: This functions are used to completely abstract raylib code from OpenGL layer, + // some of them are direct wrappers over OpenGL calls, some others are custom + //------------------------------------------------------------------------------------ + + // Vertex buffers state + EnableVertexArray :: proc(vaoId: c.uint) -> bool --- // Enable vertex array (VAO, if supported) + DisableVertexArray :: proc() --- // Disable vertex array (VAO, if supported) + EnableVertexBuffer :: proc(id: c.uint) --- // Enable vertex buffer (VBO) + DisableVertexBuffer :: proc() --- // Disable vertex buffer (VBO) + EnableVertexBufferElement :: proc(id: c.uint) --- // Enable vertex buffer element (VBO element) + DisableVertexBufferElement :: proc() --- // Disable vertex buffer element (VBO element) + EnableVertexAttribute :: proc(index: c.uint) --- // Enable vertex attribute index + DisableVertexAttribute :: proc(index: c.uint) --- // Disable vertex attribute index + when GRAPHICS_API_OPENGL_11 { + EnableStatePointer :: proc(vertexAttribType: c.int, buffer: rawptr) --- + DisableStatePointer :: proc(vertexAttribType: c.int) --- + } + + // Textures state + ActiveTextureSlot :: proc(slot: c.int) --- // Select and active a texture slot + EnableTexture :: proc(id: c.uint) --- // Enable texture + DisableTexture :: proc() --- // Disable texture + EnableTextureCubemap :: proc(id: c.uint) --- // Enable texture cubemap + DisableTextureCubemap :: proc() --- // Disable texture cubemap + TextureParameters :: proc(id: c.uint, param: c.int, value: c.int) --- // Set texture parameters (filter, wrap) + CubemapParameters :: proc(id: i32, param: c.int, value: c.int) --- // Set cubemap parameters (filter, wrap) + + // Shader state + EnableShader :: proc(id: c.uint) --- // Enable shader program + DisableShader :: proc() --- // Disable shader program + + // Framebuffer state + EnableFramebuffer :: proc(id: c.uint) --- // Enable render texture (fbo) + DisableFramebuffer :: proc() --- // Disable render texture (fbo), return to default framebuffer + ActiveDrawBuffers :: proc(count: c.int) --- // Activate multiple draw color buffers + BlitFramebuffer :: proc(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, bufferMask: c.int) --- // Blit active framebuffer to main framebuffer + + // General render state + DisableColorBlend :: proc() --- // Disable color blending + EnableDepthTest :: proc() --- // Enable depth test + DisableDepthTest :: proc() --- // Disable depth test + EnableDepthMask :: proc() --- // Enable depth write + DisableDepthMask :: proc() --- // Disable depth write + EnableBackfaceCulling :: proc() --- // Enable backface culling + DisableBackfaceCulling :: proc() --- // Disable backface culling + SetCullFace :: proc(mode: CullMode) --- // Set face culling mode + EnableScissorTest :: proc() --- // Enable scissor test + DisableScissorTest :: proc() --- // Disable scissor test + Scissor :: proc(x, y, width, height: c.int) --- // Scissor test + EnableWireMode :: proc() --- // Enable wire mode + EnablePointMode :: proc() --- // Enable point mode + DisableWireMode :: proc() --- // Disable wire and point modes + SetLineWidth :: proc(width: f32) --- // Set the line drawing width + GetLineWidth :: proc() -> f32 --- // Get the line drawing width + EnableSmoothLines :: proc() --- // Enable line aliasing + DisableSmoothLines :: proc() --- // Disable line aliasing + EnableStereoRender :: proc() --- // Enable stereo rendering + DisableStereoRender :: proc() --- // Disable stereo rendering + IsStereoRenderEnabled :: proc() -> bool --- // Check if stereo render is enabled + + + ClearColor :: proc(r, g, b, a: u8) --- // Clear color buffer with color + ClearScreenBuffers :: proc() --- // Clear used screen buffers (color and depth) + CheckErrors :: proc() --- // Check and log OpenGL error codes + SetBlendMode :: proc(mode: c.int) --- // Set blending mode + SetBlendFactors :: proc(glSrcFactor, glDstFactor, glEquation: c.int) --- // Set blending mode factor and equation (using OpenGL factors) + SetBlendFactorsSeparate :: proc(glSrcRGB, glDstRGB, glSrcAlpha, glDstAlpha, glEqRGB, glEqAlpha: c.int) --- // Set blending mode factors and equations separately (using OpenGL factors) + + //------------------------------------------------------------------------------------ + // Functions Declaration - rlgl functionality + //------------------------------------------------------------------------------------ + // rlgl initialization functions + @(link_prefix="rlgl") + Init :: proc(width, height: c.int) --- // Initialize rlgl (buffers, shaders, textures, states) + @(link_prefix="rlgl") + Close :: proc() --- // De-initialize rlgl (buffers, shaders, textures) + LoadExtensions :: proc(loader: rawptr) --- // Load OpenGL extensions (loader function required) + GetVersion :: proc() -> GlVersion --- // Get current OpenGL version + SetFramebufferWidth :: proc(width: c.int) --- // Set current framebuffer width + GetFramebufferWidth :: proc() -> c.int --- // Get default framebuffer width + SetFramebufferHeight :: proc(height: c.int) --- // Set current framebuffer height + GetFramebufferHeight :: proc() -> c.int --- // Get default framebuffer height + + + GetTextureIdDefault :: proc() -> c.uint --- // Get default texture id + GetShaderIdDefault :: proc() -> c.uint --- // Get default shader id + GetShaderLocsDefault :: proc() -> [^]c.int --- // Get default shader locations + + // Render batch management + // NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode + // but this render batch API is exposed in case of custom batches are required + LoadRenderBatch :: proc(numBuffers, bufferElements: c.int) -> RenderBatch --- // Load a render batch system + UnloadRenderBatch :: proc(batch: RenderBatch) --- // Unload render batch system + DrawRenderBatch :: proc(batch: ^RenderBatch) --- // Draw render batch data (Update->Draw->Reset) + SetRenderBatchActive :: proc(batch: ^RenderBatch) --- // Set the active render batch for rlgl (NULL for default internal) + DrawRenderBatchActive :: proc() --- // Update and draw internal render batch + CheckRenderBatchLimit :: proc(vCount: c.int) -> c.int --- // Check internal buffer overflow for a given number of vertex + + SetTexture :: proc(id: c.uint) --- // Set current texture for render batch and check buffers limits + + //------------------------------------------------------------------------------------------------------------------------ + + // Vertex buffers management + LoadVertexArray :: proc() -> c.uint --- // Load vertex array (vao) if supported + LoadVertexBuffer :: proc(buffer: rawptr, size: c.int, is_dynamic: bool) -> c.uint --- // Load a vertex buffer attribute + LoadVertexBufferElement :: proc(buffer: rawptr, size: c.int, is_dynamic: bool) -> c.uint --- // Load a new attributes element buffer + UpdateVertexBuffer :: proc(bufferId: c.uint, data: rawptr, dataSize: c.int, offset: c.int) --- // Update GPU buffer with new data + UpdateVertexBufferElements :: proc(id: c.uint, data: rawptr, dataSize: c.int, offset: c.int) --- // Update vertex buffer elements with new data + UnloadVertexArray :: proc(vaoId: c.uint) --- + UnloadVertexBuffer :: proc(vboId: c.uint) --- + SetVertexAttribute :: proc(index: c.uint, compSize: c.int, type: c.int, normalized: bool, stride: c.int, pointer: rawptr) --- + SetVertexAttributeDivisor :: proc(index: c.uint, divisor: c.int) --- + SetVertexAttributeDefault :: proc(locIndex: c.int, value: rawptr, attribType: c.int, count: c.int) --- // Set vertex attribute default value + DrawVertexArray :: proc(offset: c.int, count: c.int) --- + DrawVertexArrayElements :: proc(offset: c.int, count: c.int, buffer: rawptr) --- + DrawVertexArrayInstanced :: proc(offset: c.int, count: c.int, instances: c.int) --- + DrawVertexArrayElementsInstanced :: proc(offset: c.int, count: c.int, buffer: rawptr, instances: c.int) --- + + // Textures management + LoadTexture :: proc(data: rawptr, width, height: c.int, format: c.int, mipmapCount: c.int) -> c.uint --- // Load texture in GPU + LoadTextureDepth :: proc(width, height: c.int, useRenderBuffer: bool) -> c.uint --- // Load depth texture/renderbuffer (to be attached to fbo) + LoadTextureCubemap :: proc(data: rawptr, size: c.int, format: c.int) -> c.uint --- // Load texture cubemap + UpdateTexture :: proc(id: c.uint, offsetX, offsetY: c.int, width, height: c.int, format: c.int, data: rawptr) --- // Update GPU texture with new data + GetGlTextureFormats :: proc(format: c.int, glInternalFormat, glFormat, glType: ^c.uint) --- // Get OpenGL internal formats + GetPixelFormatName :: proc(format: c.uint) -> cstring --- // Get name string for pixel format + UnloadTexture :: proc(id: c.uint) --- // Unload texture from GPU memory + GenTextureMipmaps :: proc(id: c.uint, width, height: c.int, format: c.int, mipmaps: ^c.int) --- // Generate mipmap data for selected texture + ReadTexturePixels :: proc(id: c.uint, width, height: c.int, format: c.int) -> rawptr --- // Read texture pixel data + ReadScreenPixels :: proc(width, height: c.int) -> [^]byte --- // Read screen pixel data (color buffer) + + // Framebuffer management (fbo) + LoadFramebuffer :: proc(width, height: c.int) -> c.uint --- // Load an empty framebuffer + FramebufferAttach :: proc(fboId, texId: c.uint, attachType: c.int, texType: c.int, mipLevel: c.int) --- // Attach texture/renderbuffer to a framebuffer + FramebufferComplete :: proc(id: c.uint) -> bool --- // Verify framebuffer is complete + UnloadFramebuffer :: proc(id: c.uint) --- // Delete framebuffer from GPU + + // Shaders management + LoadShaderCode :: proc(vsCode, fsCode: cstring) -> c.uint --- // Load shader from code strings + CompileShader :: proc(shaderCode: cstring, type: c.int) -> c.uint --- // Compile custom shader and return shader id (type: VERTEX_SHADER, FRAGMENT_SHADER, COMPUTE_SHADER) + LoadShaderProgram :: proc(vShaderId, fShaderId: c.uint) -> c.uint --- // Load custom shader program + UnloadShaderProgram :: proc(id: c.uint) --- // Unload shader program + GetLocationUniform :: proc(shaderId: c.uint, uniformName: cstring) -> c.int --- // Get shader location uniform + GetLocationAttrib :: proc(shaderId: c.uint, attribName: cstring) -> c.int --- // Get shader location attribute + SetUniform :: proc(locIndex: c.int, value: rawptr, uniformType: c.int, count: c.int) --- // Set shader value uniform + SetUniformMatrix :: proc(locIndex: c.int, mat: Matrix) --- // Set shader value matrix + SetUniformSampler :: proc(locIndex: c.int, textureId: c.uint) --- // Set shader value sampler + SetShader :: proc(id: c.uint, locs: [^]c.int) --- // Set shader currently active (id and locations) + + // Compute shader management + LoadComputeShaderProgram :: proc(shaderId: c.uint) -> c.uint --- // Load compute shader program + ComputeShaderDispatch :: proc(groupX, groupY, groupZ: c.uint) --- // Dispatch compute shader (equivalent to *draw* for graphics pipeline) + + // Shader buffer storage object management (ssbo) + LoadShaderBuffer :: proc(size: c.uint, data: rawptr, usageHint: c.int) -> c.uint --- // Load shader storage buffer object (SSBO) + UnloadShaderBuffer :: proc(ssboId: c.uint) --- // Unload shader storage buffer object (SSBO) + UpdateShaderBuffer :: proc(id: c.uint, data: rawptr, dataSize: c.uint, offset: c.uint) --- // Update SSBO buffer data + BindShaderBuffer :: proc(id: c.uint, index: c.uint) --- // Bind SSBO buffer + ReadShaderBuffer :: proc(id: c.uint, dest: rawptr, count: c.uint, offset: c.uint) --- // Read SSBO buffer data (GPU->CPU) + CopyShaderBuffer :: proc(destId, srcId: c.uint, destOffset, srcOffset: c.uint, count: c.uint) --- // Copy SSBO data between buffers + GetShaderBufferSize :: proc(id: c.uint) -> c.uint --- // Get SSBO buffer size + + // Buffer management + BindImageTexture :: proc(id: c.uint, index: c.uint, format: c.int, readonly: bool) --- // Bind image texture + + // Matrix state management + GetMatrixModelview :: proc() -> Matrix --- // Get internal modelview matrix + GetMatrixProjection :: proc() -> Matrix --- // Get internal projection matrix + GetMatrixTransform :: proc() -> Matrix --- // Get internal accumulated transform matrix + GetMatrixProjectionStereo :: proc(eye: c.int) -> Matrix --- // Get internal projection matrix for stereo render (selected eye) + GetMatrixViewOffsetStereo :: proc(eye: c.int) -> Matrix --- // Get internal view offset matrix for stereo render (selected eye) + SetMatrixProjection :: proc(proj: Matrix) --- // Set a custom projection matrix (replaces internal projection matrix) + SetMatrixModelview :: proc(view: Matrix) --- // Set a custom modelview matrix (replaces internal modelview matrix) + SetMatrixProjectionStereo :: proc(right, left: Matrix) --- // Set eyes projection matrices for stereo rendering + SetMatrixViewOffsetStereo :: proc(right, left: Matrix) --- // Set eyes view offsets matrices for stereo rendering + + // Quick and dirty cube/quad buffers load->draw->unload + LoadDrawCube :: proc() --- // Load and draw a cube + LoadDrawQuad :: proc() --- // Load and draw a quad +} diff --git a/vendor/sdl2/sdl2.odin b/vendor/sdl2/sdl2.odin index 719390adc..73d95e18a 100644 --- a/vendor/sdl2/sdl2.odin +++ b/vendor/sdl2/sdl2.odin @@ -235,8 +235,8 @@ foreign lib { // quit QuitRequested :: #force_inline proc "c" () -> bool { - PumpEvents() - return bool(PeepEvents(nil, 0, .PEEKEVENT, .QUIT, .QUIT) > 0) + PumpEvents() + return bool(PeepEvents(nil, 0, .PEEKEVENT, .QUIT, .QUIT) > 0) } diff --git a/vendor/sdl2/sdl_pixels.odin b/vendor/sdl2/sdl_pixels.odin index 9eccbc6ab..195f2920f 100644 --- a/vendor/sdl2/sdl_pixels.odin +++ b/vendor/sdl2/sdl_pixels.odin @@ -22,7 +22,7 @@ PIXELTYPE_ARRAYU8 :: 7 PIXELTYPE_ARRAYU16 :: 8 PIXELTYPE_ARRAYU32 :: 9 PIXELTYPE_ARRAYF16 :: 10 -PIXELTYPE_ARRAYF3 :: 11 +PIXELTYPE_ARRAYF32 :: 11 BITMAPORDER_NONE :: 0 BITMAPORDER_4321 :: 1 @@ -47,7 +47,7 @@ ARRAYORDER_RGBA :: 2 ARRAYORDER_ARGB :: 3 ARRAYORDER_BGR :: 4 ARRAYORDER_BGRA :: 5 -ARRAYORDER_ABG :: 6 +ARRAYORDER_ABGR :: 6 PACKEDLAYOUT_NONE :: 0 PACKEDLAYOUT_332 :: 1 diff --git a/vendor/sdl2/sdl_render.odin b/vendor/sdl2/sdl_render.odin index f948b39b0..cceebf3ac 100644 --- a/vendor/sdl2/sdl_render.odin +++ b/vendor/sdl2/sdl_render.odin @@ -76,7 +76,7 @@ foreign lib { GetRenderer :: proc(window: ^Window) -> ^Renderer --- GetRendererInfo :: proc(renderer: ^Renderer, info: ^RendererInfo) -> c.int --- GetRendererOutputSize :: proc(renderer: ^Renderer, w, h: ^c.int) -> c.int --- - CreateTexture :: proc(renderer: ^Renderer, format: u32, access: TextureAccess, w, h: c.int) -> ^Texture --- + CreateTexture :: proc(renderer: ^Renderer, format: PixelFormatEnum, access: TextureAccess, w, h: c.int) -> ^Texture --- CreateTextureFromSurface :: proc(renderer: ^Renderer, surface: ^Surface) -> ^Texture --- QueryTexture :: proc(texture: ^Texture, format: ^u32, access, w, h: ^c.int) -> c.int --- SetTextureColorMod :: proc(texture: ^Texture, r, g, b: u8) -> c.int --- diff --git a/vendor/stb/image/stb_image.odin b/vendor/stb/image/stb_image.odin index c7caf801e..828a1c2bd 100644 --- a/vendor/stb/image/stb_image.odin +++ b/vendor/stb/image/stb_image.odin @@ -2,13 +2,26 @@ package stb_image import c "core:c/libc" +@(private) +LIB :: ( + "../lib/stb_image.lib" when ODIN_OS == .Windows + else "../lib/stb_image.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_image.a" when ODIN_OS == .Darwin + else "" +) + +when LIB != "" { + when !#exists(LIB) { + // The STB libraries are shipped with the compiler on Windows so a Windows specific message should not be needed. + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import stbi { LIB } +} else { + foreign import stbi "system:stb_image" +} + #assert(size_of(c.int) == size_of(b32)) - - when ODIN_OS == .Windows { foreign import stbi "../lib/stb_image.lib" } -else when ODIN_OS == .Linux { foreign import stbi "../lib/stb_image.a" } -else when ODIN_OS == .Darwin { foreign import stbi "../lib/darwin/stb_image.a" } -else { foreign import stbi "system:stb_image" } - #assert(size_of(b32) == size_of(c.int)) // diff --git a/vendor/stb/image/stb_image_resize.odin b/vendor/stb/image/stb_image_resize.odin index c464964df..d407a1852 100644 --- a/vendor/stb/image/stb_image_resize.odin +++ b/vendor/stb/image/stb_image_resize.odin @@ -2,11 +2,24 @@ package stb_image import c "core:c/libc" - when ODIN_OS == .Windows { foreign import lib "../lib/stb_image_resize.lib" } -else when ODIN_OS == .Linux { foreign import lib "../lib/stb_image_resize.a" } -else when ODIN_OS == .Darwin { foreign import lib "../lib/darwin/stb_image_resize.a" } -else { foreign import lib "system:stb_image_resize" } +@(private) +RESIZE_LIB :: ( + "../lib/stb_image_resize.lib" when ODIN_OS == .Windows + else "../lib/stb_image_resize.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_image_resize.a" when ODIN_OS == .Darwin + else "" +) +when RESIZE_LIB != "" { + when !#exists(RESIZE_LIB) { + // The STB libraries are shipped with the compiler on Windows so a Windows specific message should not be needed. + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import lib { RESIZE_LIB } +} else { + foreign import lib "system:stb_image_resize" +} ////////////////////////////////////////////////////////////////////////////// // diff --git a/vendor/stb/image/stb_image_write.odin b/vendor/stb/image/stb_image_write.odin index 9ed97eb48..f030f1e28 100644 --- a/vendor/stb/image/stb_image_write.odin +++ b/vendor/stb/image/stb_image_write.odin @@ -2,11 +2,24 @@ package stb_image import c "core:c/libc" - when ODIN_OS == .Windows { foreign import stbiw "../lib/stb_image_write.lib" } -else when ODIN_OS == .Linux { foreign import stbiw "../lib/stb_image_write.a" } -else when ODIN_OS == .Darwin { foreign import stbiw "../lib/darwin/stb_image_write.a" } -else { foreign import stbiw "system:stb_image_write" } +@(private) +WRITE_LIB :: ( + "../lib/stb_image_write.lib" when ODIN_OS == .Windows + else "../lib/stb_image_write.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_image_write.a" when ODIN_OS == .Darwin + else "" +) +when WRITE_LIB != "" { + when !#exists(WRITE_LIB) { + // The STB libraries are shipped with the compiler on Windows so a Windows specific message should not be needed. + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import stbiw { WRITE_LIB } +} else { + foreign import stbiw "system:stb_image_write" +} write_func :: proc "c" (ctx: rawptr, data: rawptr, size: c.int) diff --git a/vendor/stb/lib/stb_truetype_wasm.o b/vendor/stb/lib/stb_truetype_wasm.o new file mode 100644 index 000000000..15c4fa0d5 Binary files /dev/null and b/vendor/stb/lib/stb_truetype_wasm.o differ diff --git a/vendor/stb/rect_pack/stb_rect_pack.odin b/vendor/stb/rect_pack/stb_rect_pack.odin index 3a2544b81..6c0b56378 100644 --- a/vendor/stb/rect_pack/stb_rect_pack.odin +++ b/vendor/stb/rect_pack/stb_rect_pack.odin @@ -4,10 +4,23 @@ import "core:c" #assert(size_of(b32) == size_of(c.int)) - when ODIN_OS == .Windows { foreign import lib "../lib/stb_rect_pack.lib" } -else when ODIN_OS == .Linux { foreign import lib "../lib/stb_rect_pack.a" } -else when ODIN_OS == .Darwin { foreign import lib "../lib/darwin/stb_rect_pack.a" } -else { foreign import lib "system:stb_rect_pack" } +@(private) +LIB :: ( + "../lib/stb_rect_pack.lib" when ODIN_OS == .Windows + else "../lib/stb_rect_pack.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_rect_pack.a" when ODIN_OS == .Darwin + else "" +) + +when LIB != "" { + when !#exists(LIB) { + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import lib { LIB } +} else { + foreign import lib "system:stb_rect_pack" +} Coord :: distinct c.int _MAXVAL :: max(Coord) diff --git a/vendor/stb/src/Makefile b/vendor/stb/src/Makefile index a6db3e297..6123a95fa 100644 --- a/vendor/stb/src/Makefile +++ b/vendor/stb/src/Makefile @@ -6,6 +6,10 @@ else all: unix endif +wasm: + mkdir -p ../lib + clang -c -Os --target=wasm32 -nostdlib stb_truetype_wasm.c -o ../lib/stb_truetype_wasm.o + unix: mkdir -p ../lib $(CC) -c -O2 -Os -fPIC stb_image.c stb_image_write.c stb_image_resize.c stb_truetype.c stb_rect_pack.c stb_vorbis.c @@ -14,7 +18,7 @@ unix: $(AR) rcs ../lib/stb_image_resize.a stb_image_resize.o $(AR) rcs ../lib/stb_truetype.a stb_truetype.o $(AR) rcs ../lib/stb_rect_pack.a stb_rect_pack.o - #$(AR) rcs ../lib/stb_vorbis_pack.a stb_vorbis_pack.o + $(AR) rcs ../lib/stb_vorbis.a stb_vorbis.o #$(CC) -fPIC -shared -Wl,-soname=stb_image.so -o ../lib/stb_image.so stb_image.o #$(CC) -fPIC -shared -Wl,-soname=stb_image_write.so -o ../lib/stb_image_write.so stb_image_write.o #$(CC) -fPIC -shared -Wl,-soname=stb_image_resize.so -o ../lib/stb_image_resize.so stb_image_resize.o diff --git a/vendor/stb/src/stb_truetype_wasm.c b/vendor/stb/src/stb_truetype_wasm.c new file mode 100644 index 000000000..e0b1fdc77 --- /dev/null +++ b/vendor/stb/src/stb_truetype_wasm.c @@ -0,0 +1,46 @@ +#include + +void *stbtt_malloc(size_t size); +void stbtt_free(void *ptr); + +void stbtt_qsort(void* base, size_t num, size_t size, int (*compare)(const void*, const void*)); + +double stbtt_floor(double x); +double stbtt_ceil(double x); +double stbtt_sqrt(double x); +double stbtt_pow(double x, double y); +double stbtt_fmod(double x, double y); +double stbtt_cos(double x); +double stbtt_acos(double x); +double stbtt_fabs(double x); + +unsigned long stbtt_strlen(const char *str); + +void *memcpy(void *dst, const void *src, size_t count); +void *memset(void *dst, int x, size_t count); + +#define STBRP_SORT stbtt_qsort +#define STBRP_ASSERT(condition) ((void)0) + +#define STBTT_malloc(x,u) ((void)(u),stbtt_malloc(x)) +#define STBTT_free(x,u) ((void)(u),stbtt_free(x)) + +#define STBTT_assert(condition) ((void)0) + +#define STBTT_ifloor(x) ((int) stbtt_floor(x)) +#define STBTT_iceil(x) ((int) stbtt_ceil(x)) +#define STBTT_sqrt(x) stbtt_sqrt(x) +#define STBTT_pow(x,y) stbtt_pow(x,y) +#define STBTT_fmod(x,y) stbtt_fmod(x,y) +#define STBTT_cos(x) stbtt_cos(x) +#define STBTT_acos(x) stbtt_acos(x) +#define STBTT_fabs(x) stbtt_fabs(x) +#define STBTT_strlen(x) stbtt_strlen(x) +#define STBTT_memcpy memcpy +#define STBTT_memset memset + +#define STB_RECT_PACK_IMPLEMENTATION +#include "stb_rect_pack.h" + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" diff --git a/vendor/stb/truetype/stb_truetype.odin b/vendor/stb/truetype/stb_truetype.odin index 1600041de..e6defff5f 100644 --- a/vendor/stb/truetype/stb_truetype.odin +++ b/vendor/stb/truetype/stb_truetype.odin @@ -3,11 +3,25 @@ package stb_truetype import c "core:c" import stbrp "vendor:stb/rect_pack" - when ODIN_OS == .Windows { foreign import stbtt "../lib/stb_truetype.lib" } -else when ODIN_OS == .Linux { foreign import stbtt "../lib/stb_truetype.a" } -else when ODIN_OS == .Darwin { foreign import stbtt "../lib/darwin/stb_truetype.a" } -else { foreign import stbtt "system:stb_truetype" } +@(private) +LIB :: ( + "../lib/stb_truetype.lib" when ODIN_OS == .Windows + else "../lib/stb_truetype.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_truetype.a" when ODIN_OS == .Darwin + else "" +) +when LIB != "" { + when !#exists(LIB) { + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import stbtt { LIB } +} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { + foreign import stbtt "../lib/stb_truetype_wasm.o" +} else { + foreign import stbtt "system:stb_truetype" +} /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// @@ -554,7 +568,7 @@ foreign stbtt { // some of the values for the IDs are below; for more see the truetype spec: // http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html // http://www.microsoft.com/typography/otspec/name.htm - GetFontNameString :: proc(font: ^fontinfo, length: c.int, platformID: PLATFORM_ID, encodingID, languageID, nameID: c.int) -> cstring --- + GetFontNameString :: proc(font: ^fontinfo, length: ^c.int, platformID: PLATFORM_ID, encodingID, languageID, nameID: c.int) -> cstring --- } diff --git a/vendor/stb/truetype/stb_truetype_wasm.odin b/vendor/stb/truetype/stb_truetype_wasm.odin new file mode 100644 index 000000000..cb516bc1d --- /dev/null +++ b/vendor/stb/truetype/stb_truetype_wasm.odin @@ -0,0 +1,89 @@ +//+build wasm32, wasm64p32 +package stb_truetype + +import "base:builtin" +import "base:intrinsics" +import "base:runtime" + +import "core:c" +import "core:math" +import "core:mem" +import "core:slice" +import "core:sort" + +@(require, linkage="strong", link_name="stbtt_malloc") +malloc :: proc "c" (size: uint) -> rawptr { + context = runtime.default_context() + ptr, _ := runtime.mem_alloc_non_zeroed(int(size)) + return raw_data(ptr) +} + +@(require, linkage="strong", link_name="stbtt_free") +free :: proc "c" (ptr: rawptr) { + context = runtime.default_context() + builtin.free(ptr) +} + +@(require, linkage="strong", link_name="stbtt_qsort") +qsort :: proc "c" (base: rawptr, num: uint, size: uint, cmp: proc "c" (a, b: rawptr) -> i32) { + context = runtime.default_context() + + Inputs :: struct { + base: rawptr, + num: uint, + size: uint, + cmp: proc "c" (a, b: rawptr) -> i32, + } + + sort.sort({ + collection = &Inputs{base, num, size, cmp}, + len = proc(it: sort.Interface) -> int { + inputs := (^Inputs)(it.collection) + return int(inputs.num) + }, + less = proc(it: sort.Interface, i, j: int) -> bool { + inputs := (^Inputs)(it.collection) + a := rawptr(uintptr(inputs.base) + (uintptr(i) * uintptr(inputs.size))) + b := rawptr(uintptr(inputs.base) + (uintptr(j) * uintptr(inputs.size))) + return inputs.cmp(a, b) < 0 + }, + swap = proc(it: sort.Interface, i, j: int) { + inputs := (^Inputs)(it.collection) + + a := rawptr(uintptr(inputs.base) + (uintptr(i) * uintptr(inputs.size))) + b := rawptr(uintptr(inputs.base) + (uintptr(j) * uintptr(inputs.size))) + + slice.ptr_swap_non_overlapping(a, b, int(inputs.size)) + }, + }) +} + +@(require, linkage="strong", link_name="stbtt_floor") +floor :: proc "c" (x: f64) -> f64 { return math.floor(x) } +@(require, linkage="strong", link_name="stbtt_ceil") +ceil :: proc "c" (x: f64) -> f64 { return math.ceil(x) } +@(require, linkage="strong", link_name="stbtt_sqrt") +sqrt :: proc "c" (x: f64) -> f64 { return math.sqrt(x) } +@(require, linkage="strong", link_name="stbtt_pow") +pow :: proc "c" (x, y: f64) -> f64 { return math.pow(x, y) } + +@(require, linkage="strong", link_name="stbtt_fmod") +fmod :: proc "c" (x, y: f64) -> f64 { + context = runtime.default_context(); + // NOTE: only called in the `stbtt_GetGlyphSDF` code path. + panic("`math.round` is broken on 32 bit targets, see #3856") +} + +@(require, linkage="strong", link_name="stbtt_cos") +cos :: proc "c" (x: f64) -> f64 { return math.cos(x) } +@(require, linkage="strong", link_name="stbtt_acos") +acos :: proc "c" (x: f64) -> f64 { return math.acos(x) } +@(require, linkage="strong", link_name="stbtt_fabs") +fabs :: proc "c" (x: f64) -> f64 { return math.abs(x) } + +@(require, linkage="strong", link_name="stbtt_strlen") +strlen :: proc "c" (str: cstring) -> c.ulong { return c.ulong(len(str)) } + +// NOTE: defined in runtime. +// void *memcpy(void *dst, const void *src, size_t count); +// void *memset(void *dst, int x, size_t count); diff --git a/vendor/stb/vorbis/stb_vorbis.odin b/vendor/stb/vorbis/stb_vorbis.odin index 0c887a473..867ffb86d 100644 --- a/vendor/stb/vorbis/stb_vorbis.odin +++ b/vendor/stb/vorbis/stb_vorbis.odin @@ -2,13 +2,23 @@ package stb_vorbis import c "core:c/libc" +@(private) +LIB :: ( + "../lib/stb_vorbis.lib" when ODIN_OS == .Windows + else "../lib/stb_vorbis.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_vorbis.a" when ODIN_OS == .Darwin + else "" +) - when ODIN_OS == .Windows { foreign import lib "../lib/stb_vorbis.lib" } -else when ODIN_OS == .Linux { foreign import lib "../lib/stb_vorbis.a" } -else when ODIN_OS == .Darwin { foreign import lib "../lib/darwin/stb_vorbis.a" } -else { foreign import lib "system:stb_vorbis" } - +when LIB != "" { + when !#exists(LIB) { + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + foreign import lib { LIB } +} else { + foreign import lib "system:stb_vorbis" +} /////////// THREAD SAFETY diff --git a/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py b/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py index 65f53758f..a551887ff 100644 --- a/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py +++ b/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py @@ -94,15 +94,13 @@ def convert_type(t, prev_name, curr_name): if t == "": return t + if t.startswith("const"): + t = convert_type(t[6:], prev_name, curr_name) + elif t.endswith("*"): - elem = "" pointer = "^" - if t.startswith("const"): - ttype = t[6:len(t)-1] - elem = convert_type(ttype, prev_name, curr_name) - else: - ttype = t[:len(t)-1] - elem = convert_type(ttype, prev_name, curr_name) + ttype = t[:len(t)-1] + elem = convert_type(ttype, prev_name, curr_name) if curr_name.endswith("s") or curr_name.endswith("Table"): if prev_name.endswith("Count") or prev_name.endswith("Counts"): @@ -625,12 +623,14 @@ def parse_procedures(f): for rt, name, fields in data: proc_name = no_vk(name) - pf = [] prev_name = "" - for type_, fname in re.findall(r"(?:\s*|)(.+?)\s*(\w+)(?:,|$)", fields): + for type_, fname, array_len in re.findall(r"(?:\s*|)(.+?)\s*(\w+)(?:\[(\d+)\])?(?:,|$)", fields): curr_name = fix_arg(fname) - pf.append((do_type(type_, prev_name, curr_name), curr_name)) + ty = do_type(type_, prev_name, curr_name) + if array_len != "": + ty = f"^[{array_len}]{ty}" + pf.append((ty, curr_name)) prev_name = curr_name data_fields = ', '.join(["{}: {}".format(n, t) for t, n in pf if t != ""]) @@ -798,7 +798,7 @@ API_VERSION_1_2 :: (1<<22) | (2<<12) | (0) API_VERSION_1_3 :: (1<<22) | (3<<12) | (0) MAKE_VERSION :: proc(major, minor, patch: u32) -> u32 { - return (major<<22) | (minor<<12) | (patch) +\treturn (major<<22) | (minor<<12) | (patch) } // Base types diff --git a/vendor/vulkan/core.odin b/vendor/vulkan/core.odin index 764156b8d..79b856595 100644 --- a/vendor/vulkan/core.odin +++ b/vendor/vulkan/core.odin @@ -8,7 +8,7 @@ API_VERSION_1_2 :: (1<<22) | (2<<12) | (0) API_VERSION_1_3 :: (1<<22) | (3<<12) | (0) MAKE_VERSION :: proc(major, minor, patch: u32) -> u32 { - return (major<<22) | (minor<<12) | (patch) + return (major<<22) | (minor<<12) | (patch) } // Base types diff --git a/vendor/vulkan/procedures.odin b/vendor/vulkan/procedures.odin index 9a27b4f79..bec421f29 100644 --- a/vendor/vulkan/procedures.odin +++ b/vendor/vulkan/procedures.odin @@ -259,7 +259,7 @@ ProcCmdResolveImage2KHR :: #type proc "system ProcCmdSetAlphaToCoverageEnableEXT :: #type proc "system" (commandBuffer: CommandBuffer, alphaToCoverageEnable: b32) ProcCmdSetAlphaToOneEnableEXT :: #type proc "system" (commandBuffer: CommandBuffer, alphaToOneEnable: b32) ProcCmdSetAttachmentFeedbackLoopEnableEXT :: #type proc "system" (commandBuffer: CommandBuffer, aspectMask: ImageAspectFlags) -ProcCmdSetBlendConstants :: #type proc "system" (commandBuffer: CommandBuffer) +ProcCmdSetBlendConstants :: #type proc "system" (commandBuffer: CommandBuffer, blendConstants: ^[4]f32) ProcCmdSetCheckpointNV :: #type proc "system" (commandBuffer: CommandBuffer, pCheckpointMarker: rawptr) ProcCmdSetCoarseSampleOrderNV :: #type proc "system" (commandBuffer: CommandBuffer, sampleOrderType: CoarseSampleOrderTypeNV, customSampleOrderCount: u32, pCustomSampleOrders: [^]CoarseSampleOrderCustomNV) ProcCmdSetColorBlendAdvancedEXT :: #type proc "system" (commandBuffer: CommandBuffer, firstAttachment: u32, attachmentCount: u32, pColorBlendAdvanced: ^ColorBlendAdvancedEXT) @@ -302,8 +302,8 @@ ProcCmdSetEvent2KHR :: #type proc "system ProcCmdSetExclusiveScissorEnableNV :: #type proc "system" (commandBuffer: CommandBuffer, firstExclusiveScissor: u32, exclusiveScissorCount: u32, pExclusiveScissorEnables: [^]b32) ProcCmdSetExclusiveScissorNV :: #type proc "system" (commandBuffer: CommandBuffer, firstExclusiveScissor: u32, exclusiveScissorCount: u32, pExclusiveScissors: [^]Rect2D) ProcCmdSetExtraPrimitiveOverestimationSizeEXT :: #type proc "system" (commandBuffer: CommandBuffer, extraPrimitiveOverestimationSize: f32) -ProcCmdSetFragmentShadingRateEnumNV :: #type proc "system" (commandBuffer: CommandBuffer, shadingRate: FragmentShadingRateNV) -ProcCmdSetFragmentShadingRateKHR :: #type proc "system" (commandBuffer: CommandBuffer, pFragmentSize: ^Extent2D) +ProcCmdSetFragmentShadingRateEnumNV :: #type proc "system" (commandBuffer: CommandBuffer, shadingRate: FragmentShadingRateNV, combinerOps: ^[2]FragmentShadingRateCombinerOpKHR) +ProcCmdSetFragmentShadingRateKHR :: #type proc "system" (commandBuffer: CommandBuffer, pFragmentSize: ^Extent2D, combinerOps: ^[2]FragmentShadingRateCombinerOpKHR) ProcCmdSetFrontFace :: #type proc "system" (commandBuffer: CommandBuffer, frontFace: FrontFace) ProcCmdSetFrontFaceEXT :: #type proc "system" (commandBuffer: CommandBuffer, frontFace: FrontFace) ProcCmdSetLineRasterizationModeEXT :: #type proc "system" (commandBuffer: CommandBuffer, lineRasterizationMode: LineRasterizationModeEXT) diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index 320d74d68..74ad7568e 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -13,13 +13,18 @@ function stripNewline(str) { return str.replace(/\n/, ' ') } -const STRING_SIZE = 2*4; - class WasmMemoryInterface { constructor() { this.memory = null; this.exports = null; this.listenerMap = {}; + + // Size (in bytes) of the integer type, should be 4 on `js_wasm32` and 8 on `js_wasm64p32` + this.intSize = 4; + } + + setIntSize(size) { + this.intSize = size; } setMemory(memory) { @@ -69,21 +74,50 @@ class WasmMemoryInterface { const hi = this.mem.getInt32 (addr + 4, true); return lo + hi*4294967296; }; - loadF32(addr) { return this.mem.getFloat32(addr, true); } - loadF64(addr) { return this.mem.getFloat64(addr, true); } - loadInt(addr) { return this.mem.getInt32 (addr, true); } - loadUint(addr) { return this.mem.getUint32 (addr, true); } + loadF32(addr) { return this.mem.getFloat32(addr, true); } + loadF64(addr) { return this.mem.getFloat64(addr, true); } + loadInt(addr) { + if (this.intSize == 8) { + return this.loadI64(addr); + } else if (this.intSize == 4) { + return this.loadI32(addr); + } else { + throw new Error('Unhandled `intSize`, expected `4` or `8`'); + } + }; + loadUint(addr) { + if (this.intSize == 8) { + return this.loadU64(addr); + } else if (this.intSize == 4) { + return this.loadU32(addr); + } else { + throw new Error('Unhandled `intSize`, expected `4` or `8`'); + } + }; + loadPtr(addr) { return this.loadU32(addr); } - loadPtr(addr) { return this.loadUint(addr); } + loadB32(addr) { + return this.loadU32(addr) != 0; + } loadBytes(ptr, len) { - return new Uint8Array(this.memory.buffer, ptr, len); + return new Uint8Array(this.memory.buffer, ptr, Number(len)); } loadString(ptr, len) { - const bytes = this.loadBytes(ptr, len); + const bytes = this.loadBytes(ptr, Number(len)); return new TextDecoder().decode(bytes); } + + loadCstring(ptr) { + const start = this.loadPtr(ptr); + if (start == 0) { + return null; + } + let len = 0; + for (; this.mem.getUint8(start+len) != 0; len += 1) {} + return this.loadString(start, len); + } storeU8(addr, value) { this.mem.setUint8 (addr, value); } storeI8(addr, value) { this.mem.setInt8 (addr, value); } @@ -92,17 +126,45 @@ class WasmMemoryInterface { storeU32(addr, value) { this.mem.setUint32 (addr, value, true); } storeI32(addr, value) { this.mem.setInt32 (addr, value, true); } storeU64(addr, value) { - this.mem.setUint32(addr + 0, value, true); - this.mem.setUint32(addr + 4, Math.floor(value / 4294967296), true); + this.mem.setUint32(addr + 0, Number(value), true); + + let div = 4294967296; + if (typeof value == 'bigint') { + div = BigInt(div); + } + + this.mem.setUint32(addr + 4, Math.floor(Number(value / div)), true); } storeI64(addr, value) { - this.mem.setUint32(addr + 0, value, true); - this.mem.setInt32 (addr + 4, Math.floor(value / 4294967296), true); + this.mem.setUint32(addr + 0, Number(value), true); + + let div = 4294967296; + if (typeof value == 'bigint') { + div = BigInt(div); + } + + this.mem.setInt32(addr + 4, Math.floor(Number(value / div)), true); + } + storeF32(addr, value) { this.mem.setFloat32(addr, value, true); } + storeF64(addr, value) { this.mem.setFloat64(addr, value, true); } + storeInt(addr, value) { + if (this.intSize == 8) { + this.storeI64(addr, value); + } else if (this.intSize == 4) { + this.storeI32(addr, value); + } else { + throw new Error('Unhandled `intSize`, expected `4` or `8`'); + } + } + storeUint(addr, value) { + if (this.intSize == 8) { + this.storeU64(addr, value); + } else if (this.intSize == 4) { + this.storeU32(addr, value); + } else { + throw new Error('Unhandled `intSize`, expected `4` or `8`'); + } } - storeF32(addr, value) { this.mem.setFloat32(addr, value, true); } - storeF64(addr, value) { this.mem.setFloat64(addr, value, true); } - storeInt(addr, value) { this.mem.setInt32 (addr, value, true); } - storeUint(addr, value) { this.mem.setUint32 (addr, value, true); } // Returned length might not be the same as `value.length` if non-ascii strings are given. storeString(addr, value) { @@ -209,10 +271,11 @@ class WebGLInterface { } } getSource(shader, strings_ptr, strings_length) { + const stringSize = this.mem.intSize*2; let source = ""; for (let i = 0; i < strings_length; i++) { - let ptr = this.mem.loadPtr(strings_ptr + i*STRING_SIZE); - let len = this.mem.loadPtr(strings_ptr + i*STRING_SIZE + 4); + let ptr = this.mem.loadPtr(strings_ptr + i*stringSize); + let len = this.mem.loadPtr(strings_ptr + i*stringSize + 4); let str = this.mem.loadString(ptr, len); source += str; } @@ -1119,10 +1182,11 @@ class WebGLInterface { }, TransformFeedbackVaryings: (program, varyings_ptr, varyings_len, bufferMode) => { this.assertWebGL2(); + const stringSize = this.mem.intSize*2; let varyings = []; for (let i = 0; i < varyings_len; i++) { - let ptr = this.mem.loadPtr(varyings_ptr + i*STRING_SIZE + 0*4); - let len = this.mem.loadPtr(varyings_ptr + i*STRING_SIZE + 1*4); + let ptr = this.mem.loadPtr(varyings_ptr + i*stringSize + 0*4); + let len = this.mem.loadPtr(varyings_ptr + i*stringSize + 1*4); varyings.push(this.mem.loadString(ptr, len)); } this.ctx.transformFeedbackVaryings(this.programs[program], varyings, bufferMode); @@ -1195,7 +1259,7 @@ class WebGLInterface { }; -function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { +function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { const MAX_INFO_CONSOLE_LINES = 512; let infoConsoleLines = new Array(); let currentLine = {}; @@ -1316,8 +1380,15 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { let event_temp_data = {}; let webglContext = new WebGLInterface(wasmMemoryInterface); + + const env = {}; + + if (memory) { + env.memory = memory; + } + return { - "env": {}, + env, "odin_env": { write: (fd, ptr, len) => { const str = wasmMemoryInterface.loadString(ptr, len); @@ -1361,7 +1432,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { }, "odin_dom": { init_event_raw: (ep) => { - const W = 4; + const W = wasmMemoryInterface.intSize; let offset = ep; let off = (amount, alignment) => { if (alignment === undefined) { @@ -1375,6 +1446,13 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { return x; }; + let align = (alignment) => { + const modulo = offset & (alignment-1); + if (modulo != 0) { + offset += alignment - modulo + } + }; + let wmi = wasmMemoryInterface; let e = event_temp_data.event; @@ -1395,10 +1473,12 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { wmi.storeU32(off(4), 0); } - wmi.storeUint(off(W), event_temp_data.id_ptr); - wmi.storeUint(off(W), event_temp_data.id_len); - wmi.storeUint(off(W), 0); // padding + align(W); + wmi.storeI32(off(W), event_temp_data.id_ptr); + wmi.storeUint(off(W), event_temp_data.id_len); + + align(8); wmi.storeF64(off(8), e.timeStamp*1e-3); wmi.storeU8(off(1), e.eventPhase); @@ -1410,8 +1490,13 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { wmi.storeU8(off(1), !!e.isComposing); wmi.storeU8(off(1), !!e.isTrusted); - let base = off(0, 8); - if (e instanceof MouseEvent) { + align(8); + if (e instanceof WheelEvent) { + wmi.storeF64(off(8), e.deltaX); + wmi.storeF64(off(8), e.deltaY); + wmi.storeF64(off(8), e.deltaZ); + wmi.storeU32(off(4), e.deltaMode); + } else if (e instanceof MouseEvent) { wmi.storeI64(off(8), e.screenX); wmi.storeI64(off(8), e.screenY); wmi.storeI64(off(8), e.clientX); @@ -1450,11 +1535,6 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { wmi.storeI32(off(W), e.code.length) wmi.storeString(off(16, 1), e.key); wmi.storeString(off(16, 1), e.code); - } else if (e instanceof WheelEvent) { - wmi.storeF64(off(8), e.deltaX); - wmi.storeF64(off(8), e.deltaY); - wmi.storeF64(off(8), e.deltaZ); - wmi.storeU32(off(4), e.deltaMode); } else if (e.type === 'scroll') { wmi.storeF64(off(8), window.scrollX); wmi.storeF64(off(8), window.scrollY); @@ -1657,10 +1737,20 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { }; }; -async function runWasm(wasmPath, consoleElement, extraForeignImports) { - let wasmMemoryInterface = new WasmMemoryInterface(); +/** + * @param {string} wasmPath - Path to the WASM module to run + * @param {?HTMLPreElement} consoleElement - Optional console/pre element to append output to, in addition to the console + * @param {any} extraForeignImports - Imports, in addition to the default runtime to provide the module + * @param {?WasmMemoryInterface} wasmMemoryInterface - Optional memory to use instead of the defaults + * @param {?int} intSize - Size (in bytes) of the integer type, should be 4 on `js_wasm32` and 8 on `js_wasm64p32` + */ +async function runWasm(wasmPath, consoleElement, extraForeignImports, wasmMemoryInterface, intSize = 4) { + if (!wasmMemoryInterface) { + wasmMemoryInterface = new WasmMemoryInterface(); + } + wasmMemoryInterface.setIntSize(intSize); - let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement); + let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement, wasmMemoryInterface.memory); let exports = {}; if (extraForeignImports !== undefined) { @@ -1675,11 +1765,17 @@ async function runWasm(wasmPath, consoleElement, extraForeignImports) { const wasm = await WebAssembly.instantiate(file, imports); exports = wasm.instance.exports; wasmMemoryInterface.setExports(exports); - wasmMemoryInterface.setMemory(exports.memory); + + if (exports.memory) { + if (wasmMemoryInterface.memory) { + console.warn("WASM module exports memory, but `runWasm` was given an interface with existing memory too"); + } + wasmMemoryInterface.setMemory(exports.memory); + } exports._start(); - // Define a `@export step :: proc(dt: f32) -> (continue: bool) {` + // Define a `@export step :: proc(dt: f32) -> (keep_going: bool) {` // in your app and it will get called every frame. // return `false` to stop the execution of the module. if (exports.step) { diff --git a/vendor/wgpu/.gitignore b/vendor/wgpu/.gitignore new file mode 100644 index 000000000..330d70755 --- /dev/null +++ b/vendor/wgpu/.gitignore @@ -0,0 +1,9 @@ +lib/* +!lib/.gitkeep +example/web/triangle.wasm +example/web/wgpu.js +example/web/runtime.js +example/example +example/example.exe +example/triangle +example/triangle.exe diff --git a/vendor/wgpu/README.md b/vendor/wgpu/README.md new file mode 100644 index 000000000..59b31567f --- /dev/null +++ b/vendor/wgpu/README.md @@ -0,0 +1,54 @@ +# WGPU + +A cross-platform (and WASM) GPU API. + +WASM support is achieved by providing wrappers around the browser native WebGPU API +that are called instead of the [wgpu-native](https://github.com/gfx-rs/wgpu-native) library, +the wgpu-native library provides support for all other targets. + +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. + +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`), +you can set `-define:WGPU_DEBUG=true` for it to look for a debug version, +and use `-define:WGPU_SHARED=true` to look for the shared libraries. + +## WASM + +For WASM, the module has to be built with a function table to enable callbacks. +You can do so using `-extra-linker-flags:"--export-table"`. + +Being able to allocate is also required (for some auxiliary APIs but also for mapping/unmapping buffers). + +You can set the context that is used for allocations by setting the global variable `wpgu.g_context`. +It will default to the `runtime.default_context`. + +Again, have a look at the `example/` and how it is set up, doing the `--import-memory` and the likes +is not strictly necessary but allows your app more memory than the minimal default. + +The bindings work on both `-target:js_wasm32` and `-target:js_wasm64p32`. + +## GLFW Glue + +There is an inner package `glfwglue` that can be used to glue together WGPU and GLFW. +It exports one procedure `GetSurface(wgpu.Instance, glfw.WindowHandle) -> glfw.Surface`. +The procedure will call the needed target specific procedures and return a surface configured +for the given window. + +Do note that wgpu does not require GLFW, you can use native windows or another windowing library too. +For that you can take inspiration from `glfwglue` on glueing them together. + +### Wayland + +GLFW supports Wayland from version 3.4 onwards and only if it is compiled with `-DGLFW_EXPOSE_NATIVE_WAYLAND`. + +Odin links against your system's glfw library (probably installed through a package manager). +If that version is lower than 3.4 or hasn't been compiled with the previously mentioned define, +you will have to compile glfw from source yourself and adjust the `foreign import` declarations in `vendor:glfw/bindings` to +point to it. diff --git a/vendor/wgpu/example/Makefile b/vendor/wgpu/example/Makefile new file mode 100644 index 000000000..f19997881 --- /dev/null +++ b/vendor/wgpu/example/Makefile @@ -0,0 +1,17 @@ +FILES := $(wildcard *) + +# NOTE: changing this requires changing the same values in the `web/index.html`. +INITIAL_MEMORY_PAGES := 2000 +MAX_MEMORY_PAGES := 65536 + +PAGE_SIZE := 65536 +INITIAL_MEMORY_BYTES := $(shell expr $(INITIAL_MEMORY_PAGES) \* $(PAGE_SIZE)) +MAX_MEMORY_BYTES := $(shell expr $(MAX_MEMORY_PAGES) \* $(PAGE_SIZE)) + +web/triangle.wasm: $(FILES) ../wgpu.js ../../wasm/js/runtime.js + odin build . \ + -target:js_wasm32 -out:web/triangle.wasm -o:size \ + -extra-linker-flags:"--export-table --import-memory --initial-memory=$(INITIAL_MEMORY_BYTES) --max-memory=$(MAX_MEMORY_BYTES)" + + cp ../wgpu.js web/wgpu.js + cp ../../wasm/js/runtime.js web/runtime.js diff --git a/vendor/wgpu/example/build.bat b/vendor/wgpu/example/build.bat new file mode 100644 index 000000000..cd3ca63ba --- /dev/null +++ b/vendor/wgpu/example/build.bat @@ -0,0 +1,12 @@ +REM NOTE: changing this requires changing the same values in the `web/index.html`. +set INITIAL_MEMORY_PAGES=2000 +set MAX_MEMORY_PAGES=65536 + +set PAGE_SIZE=65536 +set /a INITIAL_MEMORY_BYTES=%INITIAL_MEMORY_PAGES% * %PAGE_SIZE% +set /a MAX_MEMORY_BYTES=%MAX_MEMORY_PAGES% * %PAGE_SIZE% + +call odin.exe build . -target:js_wasm32 -out:web/triangle.wasm -o:size -extra-linker-flags:"--export-table --import-memory --initial-memory=%INITIAL_MEMORY_BYTES% --max-memory=%MAX_MEMORY_BYTES%" + +copy "..\wgpu.js" "web\wgpu.js" +copy "..\..\wasm\js\runtime.js" "web\runtime.js" \ No newline at end of file diff --git a/vendor/wgpu/example/main.odin b/vendor/wgpu/example/main.odin new file mode 100644 index 000000000..39161311c --- /dev/null +++ b/vendor/wgpu/example/main.odin @@ -0,0 +1,187 @@ +package vendor_wgpu_example_triangle + +import "base:runtime" + +import "core:fmt" + +import "vendor:wgpu" + +State :: struct { + ctx: runtime.Context, + os: OS, + + instance: wgpu.Instance, + surface: wgpu.Surface, + adapter: wgpu.Adapter, + device: wgpu.Device, + config: wgpu.SurfaceConfiguration, + queue: wgpu.Queue, + module: wgpu.ShaderModule, + pipeline_layout: wgpu.PipelineLayout, + pipeline: wgpu.RenderPipeline, +} + +@(private="file") +state: State + +main :: proc() { + state.ctx = context + + os_init(&state.os) + + state.instance = wgpu.CreateInstance(nil) + if state.instance == nil { + panic("WebGPU is not supported") + } + state.surface = os_get_surface(&state.os, state.instance) + + wgpu.InstanceRequestAdapter(state.instance, &{ compatibleSurface = state.surface }, on_adapter, nil) + + on_adapter :: proc "c" (status: wgpu.RequestAdapterStatus, adapter: wgpu.Adapter, message: cstring, userdata: rawptr) { + context = state.ctx + if status != .Success || adapter == nil { + fmt.panicf("request adapter failure: [%v] %s", status, message) + } + state.adapter = adapter + wgpu.AdapterRequestDevice(adapter, nil, on_device) + } + + on_device :: proc "c" (status: wgpu.RequestDeviceStatus, device: wgpu.Device, message: cstring, userdata: rawptr) { + context = state.ctx + if status != .Success || device == nil { + fmt.panicf("request device failure: [%v] %s", status, message) + } + state.device = device + + width, height := os_get_render_bounds(&state.os) + + state.config = wgpu.SurfaceConfiguration { + device = state.device, + usage = { .RenderAttachment }, + format = .BGRA8Unorm, + width = width, + height = height, + presentMode = .Fifo, + alphaMode = .Opaque, + } + wgpu.SurfaceConfigure(state.surface, &state.config) + + state.queue = wgpu.DeviceGetQueue(state.device) + + shader :: ` + @vertex + fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { + let x = f32(i32(in_vertex_index) - 1); + let y = f32(i32(in_vertex_index & 1u) * 2 - 1); + return vec4(x, y, 0.0, 1.0); + } + + @fragment + fn fs_main() -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); + }` + + state.module = wgpu.DeviceCreateShaderModule(state.device, &{ + nextInChain = &wgpu.ShaderModuleWGSLDescriptor{ + sType = .ShaderModuleWGSLDescriptor, + code = shader, + }, + }) + + state.pipeline_layout = wgpu.DeviceCreatePipelineLayout(state.device, &{}) + state.pipeline = wgpu.DeviceCreateRenderPipeline(state.device, &{ + layout = state.pipeline_layout, + vertex = { + module = state.module, + entryPoint = "vs_main", + }, + fragment = &{ + module = state.module, + entryPoint = "fs_main", + targetCount = 1, + targets = &wgpu.ColorTargetState{ + format = .BGRA8Unorm, + writeMask = wgpu.ColorWriteMaskFlags_All, + }, + }, + primitive = { + topology = .TriangleList, + + }, + multisample = { + count = 1, + mask = 0xFFFFFFFF, + }, + }) + + os_run(&state.os) + } +} + +resize :: proc "c" () { + context = state.ctx + + state.config.width, state.config.height = os_get_render_bounds(&state.os) + wgpu.SurfaceConfigure(state.surface, &state.config) +} + +frame :: proc "c" (dt: f32) { + context = state.ctx + + surface_texture := wgpu.SurfaceGetCurrentTexture(state.surface) + switch surface_texture.status { + case .Success: + // All good, could check for `surface_texture.suboptimal` here. + case .Timeout, .Outdated, .Lost: + // Skip this frame, and re-configure surface. + if surface_texture.texture != nil { + wgpu.TextureRelease(surface_texture.texture) + } + resize() + return + case .OutOfMemory, .DeviceLost: + // Fatal error + fmt.panicf("[triangle] get_current_texture status=%v", surface_texture.status) + } + defer wgpu.TextureRelease(surface_texture.texture) + + frame := wgpu.TextureCreateView(surface_texture.texture, nil) + defer wgpu.TextureViewRelease(frame) + + command_encoder := wgpu.DeviceCreateCommandEncoder(state.device, nil) + defer wgpu.CommandEncoderRelease(command_encoder) + + render_pass_encoder := wgpu.CommandEncoderBeginRenderPass( + command_encoder, &{ + colorAttachmentCount = 1, + colorAttachments = &wgpu.RenderPassColorAttachment{ + view = frame, + loadOp = .Clear, + storeOp = .Store, + clearValue = { r = 0, g = 1, b = 0, a = 1 }, + }, + }, + ) + defer wgpu.RenderPassEncoderRelease(render_pass_encoder) + + wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline) + wgpu.RenderPassEncoderDraw(render_pass_encoder, vertexCount=3, instanceCount=1, firstVertex=0, firstInstance=0) + wgpu.RenderPassEncoderEnd(render_pass_encoder) + + command_buffer := wgpu.CommandEncoderFinish(command_encoder, nil) + defer wgpu.CommandBufferRelease(command_buffer) + + wgpu.QueueSubmit(state.queue, { command_buffer }) + wgpu.SurfacePresent(state.surface) +} + +finish :: proc() { + wgpu.RenderPipelineRelease(state.pipeline) + wgpu.PipelineLayoutRelease(state.pipeline_layout) + wgpu.ShaderModuleRelease(state.module) + wgpu.QueueRelease(state.queue) + wgpu.DeviceRelease(state.device) + wgpu.AdapterRelease(state.adapter) + wgpu.SurfaceRelease(state.surface) + wgpu.InstanceRelease(state.instance) +} diff --git a/vendor/wgpu/example/os_glfw.odin b/vendor/wgpu/example/os_glfw.odin new file mode 100644 index 000000000..2b1817fa5 --- /dev/null +++ b/vendor/wgpu/example/os_glfw.odin @@ -0,0 +1,55 @@ +//+build !js +package vendor_wgpu_example_triangle + +import "core:time" + +import "vendor:glfw" +import "vendor:wgpu" +import "vendor:wgpu/glfwglue" + +OS :: struct { + window: glfw.WindowHandle, +} + +os_init :: proc(os: ^OS) { + if !glfw.Init() { + panic("[glfw] init failure") + } + + glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API) + os.window = glfw.CreateWindow(960, 540, "WGPU Native Triangle", nil, nil) + + glfw.SetFramebufferSizeCallback(os.window, size_callback) +} + +os_run :: proc(os: ^OS) { + dt: f32 + + for !glfw.WindowShouldClose(os.window) { + start := time.tick_now() + + glfw.PollEvents() + frame(dt) + + dt = f32(time.duration_seconds(time.tick_since(start))) + } + + finish() + + glfw.DestroyWindow(os.window) + glfw.Terminate() +} + +os_get_render_bounds :: proc(os: ^OS) -> (width, height: u32) { + iw, ih := glfw.GetWindowSize(os.window) + return u32(iw), u32(ih) +} + +os_get_surface :: proc(os: ^OS, instance: wgpu.Instance) -> wgpu.Surface { + return glfwglue.GetSurface(instance, os.window) +} + +@(private="file") +size_callback :: proc "c" (window: glfw.WindowHandle, width, height: i32) { + resize() +} diff --git a/vendor/wgpu/example/os_js.odin b/vendor/wgpu/example/os_js.odin new file mode 100644 index 000000000..9634f4afe --- /dev/null +++ b/vendor/wgpu/example/os_js.odin @@ -0,0 +1,60 @@ +package vendor_wgpu_example_triangle + +import "vendor:wgpu" +import "vendor:wasm/js" + +OS :: struct { + initialized: bool, +} + +@(private="file") +g_os: ^OS + +os_init :: proc(os: ^OS) { + g_os = os + assert(js.add_window_event_listener(.Resize, nil, size_callback)) +} + +// NOTE: frame loop is done by the runtime.js repeatedly calling `step`. +os_run :: proc(os: ^OS) { + os.initialized = true +} + +os_get_render_bounds :: proc(os: ^OS) -> (width, height: u32) { + rect := js.get_bounding_client_rect("body") + return u32(rect.width), u32(rect.height) +} + +os_get_surface :: proc(os: ^OS, instance: wgpu.Instance) -> wgpu.Surface { + return wgpu.InstanceCreateSurface( + instance, + &wgpu.SurfaceDescriptor{ + nextInChain = &wgpu.SurfaceDescriptorFromCanvasHTMLSelector{ + sType = .SurfaceDescriptorFromCanvasHTMLSelector, + selector = "#wgpu-canvas", + }, + }, + ) +} + +@(private="file", export) +step :: proc(dt: f32) -> bool { + if !g_os.initialized { + return true + } + + frame(dt) + return true +} + +@(private="file", fini) +os_fini :: proc() { + js.remove_window_event_listener(.Resize, nil, size_callback) + + finish() +} + +@(private="file") +size_callback :: proc(e: js.Event) { + resize() +} diff --git a/vendor/wgpu/example/web/index.html b/vendor/wgpu/example/web/index.html new file mode 100644 index 000000000..61872e35a --- /dev/null +++ b/vendor/wgpu/example/web/index.html @@ -0,0 +1,23 @@ + + + + + + WGPU WASM Triangle + + + + + + + + + diff --git a/vendor/wgpu/glfwglue/glue.odin b/vendor/wgpu/glfwglue/glue.odin new file mode 100644 index 000000000..83c497543 --- /dev/null +++ b/vendor/wgpu/glfwglue/glue.odin @@ -0,0 +1,6 @@ +//+build !linux +//+build !windows +//+build !darwin +package wgpu_glfw_glue + +#panic("package wgpu/glfwglue is not supported on the current target") diff --git a/vendor/wgpu/glfwglue/glue_darwin.odin b/vendor/wgpu/glfwglue/glue_darwin.odin new file mode 100644 index 000000000..c1477f4b0 --- /dev/null +++ b/vendor/wgpu/glfwglue/glue_darwin.odin @@ -0,0 +1,23 @@ +package wgpu_glfw_glue + +import "vendor:glfw" +import "vendor:wgpu" +import CA "vendor:darwin/QuartzCore" + +GetSurface :: proc(instance: wgpu.Instance, window: glfw.WindowHandle) -> wgpu.Surface { + ns_window := glfw.GetCocoaWindow(window) + ns_window->contentView()->setWantsLayer(true) + metal_layer := CA.MetalLayer_layer() + ns_window->contentView()->setLayer(metal_layer) + return wgpu.InstanceCreateSurface( + instance, + &wgpu.SurfaceDescriptor{ + nextInChain = &wgpu.SurfaceDescriptorFromMetalLayer{ + chain = wgpu.ChainedStruct{ + sType = .SurfaceDescriptorFromMetalLayer, + }, + layer = rawptr(metal_layer), + }, + }, + ) +} diff --git a/vendor/wgpu/glfwglue/glue_linux.odin b/vendor/wgpu/glfwglue/glue_linux.odin new file mode 100644 index 000000000..45d29a638 --- /dev/null +++ b/vendor/wgpu/glfwglue/glue_linux.odin @@ -0,0 +1,44 @@ +package wgpu_glfw_glue + +import "vendor:glfw" +import "vendor:wgpu" + +GetSurface :: proc(instance: wgpu.Instance, window: glfw.WindowHandle) -> wgpu.Surface { + if glfw.GetPlatform != nil { + if glfw.GetPlatform() == glfw.PLATFORM_WAYLAND { + display := glfw.GetWaylandDisplay() + surface := glfw.GetWaylandWindow(window) + return wgpu.InstanceCreateSurface( + instance, + &wgpu.SurfaceDescriptor{ + nextInChain = &wgpu.SurfaceDescriptorFromWaylandSurface{ + chain = { + sType = .SurfaceDescriptorFromWaylandSurface, + }, + display = display, + surface = surface, + }, + }, + ) + } + + if glfw.GetPlatform() != glfw.PLATFORM_X11 { + panic("wgpu glfw glue: unsupported platform, expected Wayland or X11") + } + } + + display := glfw.GetX11Display() + window := glfw.GetX11Window(window) + return wgpu.InstanceCreateSurface( + instance, + &wgpu.SurfaceDescriptor{ + nextInChain = &wgpu.SurfaceDescriptorFromXlibWindow{ + chain = { + sType = .SurfaceDescriptorFromXlibWindow, + }, + display = display, + window = u64(window), + }, + }, + ) +} diff --git a/vendor/wgpu/glfwglue/glue_windows.odin b/vendor/wgpu/glfwglue/glue_windows.odin new file mode 100644 index 000000000..73a933f37 --- /dev/null +++ b/vendor/wgpu/glfwglue/glue_windows.odin @@ -0,0 +1,23 @@ +package wgpu_glfw_glue + +import win "core:sys/windows" + +import "vendor:glfw" +import "vendor:wgpu" + +GetSurface :: proc(instance: wgpu.Instance, window: glfw.WindowHandle) -> wgpu.Surface { + hwnd := glfw.GetWin32Window(window) + hinstance := win.GetModuleHandleW(nil) + return wgpu.InstanceCreateSurface( + instance, + &wgpu.SurfaceDescriptor{ + nextInChain = &wgpu.SurfaceDescriptorFromWindowsHWND{ + chain = wgpu.ChainedStruct{ + sType = .SurfaceDescriptorFromWindowsHWND, + }, + hinstance = rawptr(hinstance), + hwnd = rawptr(hwnd), + }, + }, + ) +} diff --git a/vendor/wgpu/lib/.gitkeep b/vendor/wgpu/lib/.gitkeep new file mode 100644 index 000000000..e69de29bb 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 new file mode 100644 index 000000000..650260bfc Binary files /dev/null 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 new file mode 100644 index 000000000..b838c2695 Binary files /dev/null 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 new file mode 100644 index 000000000..ea4044d81 Binary files /dev/null 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 new file mode 100644 index 000000000..b30090276 Binary files /dev/null and b/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.pdb differ diff --git a/vendor/wgpu/wgpu.js b/vendor/wgpu/wgpu.js new file mode 100644 index 000000000..4fe78c992 --- /dev/null +++ b/vendor/wgpu/wgpu.js @@ -0,0 +1,2916 @@ +(function() { + +/** + * Assumptions: + * - Ability to allocate memory, set the context to allocate with using the global `wgpu.g_context` + * - Exports a function table (for callbacks), added with `-extra-linker-flags:"--export-table"` + */ +class WebGPUInterface { + + /** + * @param {WasmMemoryInterface} mem + */ + constructor(mem) { + this.mem = mem; + + this.enums = { + FeatureName: [undefined, "depth-clip-control", "depth32float-stencil8", "timestamp-query", "texture-compression-bc", "texture-compression-etc2", "texture-compression-astc", "indirect-first-instance", "shader-f16", "rg11b10ufloat-renderable", "bgra8unorm-storage", "float32-filterable", ], + StoreOp: [undefined, "store", "discard", ], + LoadOp: [undefined, "clear", "load", ], + BufferBindingType: [undefined, "uniform", "storage", "read-only-storage", ], + SamplerBindingType: [undefined, "filtering", "non-filtering", "comparison", ], + TextureSampleType: [undefined, "float", "unfilterable-float", "depth", "sint", "uint", ], + TextureViewDimension: [undefined, "1d", "2d", "2d-array", "cube", "cube-array", "3d", ], + StorageTextureAccess: [undefined, "write-only", "read-only", "read-write", ], + TextureFormat: [undefined, "r8unorm", "r8snorm", "r8uint", "r8sint", "r16uint", "r16sint", "r16float", "rg8unorm", "rg8snorm", "rg8uint", "rg8sint", "r32float", "r32uint", "r32sint", "rg16uint", "rg16sint", "rg16float", "rgba8unorm", "rgba8unorm-srgb", "rgba8snorm", "rgba8uint", "rgba8sint", "bgra8unorm", "bgra8unorm-srgb", "rgb10a2uint", "rgb10a2unorm", "rg11b10ufloat", "rgb9e5ufloat", "rg32float", "rg32uint", "rg32sint", "rgba16uint", "rgba16sint", "rgba16float", "rgba32float", "rgba32uint", "rgba32sint", "stencil8", "depth16unorm", "depth24plus", "depth24plus-stencil8", "depth32float", "depth32float-stencil8", "bc1-rgba-unorm", "bc1-rgba-unorm-srgb", "bc2-rgba-unorm", "bc2-rgba-unorm-srgb", "bc3-rgba-unorm", "bc3-rgba-unorm-srgb", "bc4-r-unorm", "bc4-r-snorm", "bc5-rg-unorm", "bc5-rg-snorm", "bc6h-rgb-ufloat", "bc6h-rgb-float", "bc7-rgba-unorm", "bc7-rgba-unorm-srgb", "etc2-rgb8unorm", "etc2-rgb8unorm-srgb", "etc2-rgb8a1unorm", "etc2-rgb8a1unorm-srgb", "etc2-rgba8unorm", "etc2-rgba8unorm-srgb", "eac-r11unorm", "eac-r11snorm", "eac-rg11unorm", "eac-rg11snorm", "astc-4x4-unorm", "astc-4x4-unorm-srgb", "astc-5x4-unorm", "astc-5x4-unorm-srgb", "astc-5x5-unorm", "astc-5x5-unorm-srgb", "astc-6x5-unorm", "astc-6x5-unorm-srgb", "astc-6x6-unorm", "astc-6x6-unorm-srgb", "astc-8x5-unorm", "astc-8x5-unorm-srgb", "astc-8x6-unorm", "astc-8x6-unorm-srgb", "astc-8x8-unorm", "astc-8x8-unorm-srgb", "astc-10x5-unorm", "astc-10x5-unorm-srgb", "astc-10x6-unorm", "astc-10x6-unorm-srgb", "astc-10x8-unorm", "astc-10x8-unorm-srgb", "astc-10x10-unorm", "astc-10x10-unorm-srgb", "astc-12x10-unorm", "astc-12x10-unorm-srgb", "astc-12x12-unorm", "astc-12x12-unorm-srgb", ], + QueryType: ["occlusion", "timestamp", ], + VertexStepMode: ["vertex", "instance", "vertex-buffer-not-used", ], + VertexFormat: [undefined, "uint8x2", "uint8x4", "sint8x2", "sint8x4", "unorm8x2", "unorm8x4", "snorm8x2", "snorm8x4", "uint16x2", "uint16x4", "sint16x2", "sint16x4", "unorm16x2", "unorm16x4", "snorm16x2", "snorm16x4", "float16x2", "float16x4", "float32", "float32x2", "float32x3", "float32x4", "uint32", "uint32x2", "uint32x3", "uint32x4", "sint32", "sint32x2", "sint32x3", "sint32x4", ], + PrimitiveTopology: ["point-list", "line-list", "line-strip", "triangle-list", "triangle-strip", ], + IndexFormat: [undefined, "uint16", "uint32", ], + FrontFace: ["ccw", "cw", ], + CullMode: ["none", "front", "back", ], + AddressMode: ["repeat", "mirror-repeat", "clamp-to-edge", ], + FilterMode: ["nearest", "linear", ], + MipmapFilterMode: ["nearest", "linear", ], + CompareFunction: [undefined, "never", "less", "less-equal", "greater", "greater-equal", "equal", "not-equal", "always", ], + TextureDimension: ["1d", "2d", "3d", ], + ErrorType: ["no-error", "validation", "out-of-memory", "internal", "unknown", "device-lost", ], + WGSLFeatureName: [undefined, "readonly_and_readwrite_storage_textures", "packed_4x8_integer_dot_product", "unrestricted_pointer_parameters", "pointer_composite_access", ], + PowerPreference: [undefined, "low-power", "high-performance", ], + CompositeAlphaMode: ["auto", "opaque", "premultiplied", "unpremultiplied", "inherit", ], + StencilOperation: ["keep", "zero", "replace", "invert", "increment-clamp", "decrement-clamp", "increment-wrap", "decrement-wrap", ], + BlendOperation: ["add", "subtract", "reverse-subtract", "min", "max", ], + 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"], + }; + + /** @type {WebGPUObjectManager<{}>} */ + this.instances = new WebGPUObjectManager("Instance", this.mem); + + /** @type {WebGPUObjectManager} */ + this.adapters = new WebGPUObjectManager("Adapter", this.mem); + + /** @type {WebGPUObjectManager} */ + this.bindGroups = new WebGPUObjectManager("BindGroup", this.mem); + + /** @type {WebGPUObjectManager} */ + this.bindGroupLayouts = new WebGPUObjectManager("BindGroupLayout", this.mem); + + /** @type {WebGPUObjectManager<{ buffer: GPUBuffer, mapping: ?{ range: ArrayBuffer, ptr: number, size: number } }>} */ + this.buffers = new WebGPUObjectManager("Buffer", this.mem); + + /** @type {WebGPUObjectManager} */ + this.devices = new WebGPUObjectManager("Device", this.mem); + + /** @type {WebGPUObjectManager} */ + this.commandBuffers = new WebGPUObjectManager("CommandBuffer", this.mem); + + /** @type {WebGPUObjectManager} */ + this.commandEncoders = new WebGPUObjectManager("CommandEncoder", this.mem); + + /** @type {WebGPUObjectManager} */ + this.computePassEncoders = new WebGPUObjectManager("ComputePassEncoder", this.mem); + + /** @type {WebGPUObjectManager} */ + this.renderPassEncoders = new WebGPUObjectManager("RenderPassEncoder", this.mem); + + /** @type {WebGPUObjectManager} */ + this.querySets = new WebGPUObjectManager("QuerySet", this.mem); + + /** @type {WebGPUObjectManager} */ + this.computePipelines = new WebGPUObjectManager("ComputePipeline", this.mem); + + /** @type {WebGPUObjectManager} */ + this.pipelineLayouts = new WebGPUObjectManager("PipelineLayout", this.mem); + + /** @type {WebGPUObjectManager} */ + this.queues = new WebGPUObjectManager("Queue", this.mem); + + /** @type {WebGPUObjectManager} */ + this.renderBundles = new WebGPUObjectManager("RenderBundle", this.mem); + + /** @type {WebGPUObjectManager} */ + this.renderBundleEncoders = new WebGPUObjectManager("RenderBundleEncoder", this.mem); + + /** @type {WebGPUObjectManager} */ + this.renderPipelines = new WebGPUObjectManager("RenderPipeline", this.mem); + + /** @type {WebGPUObjectManager} */ + this.samplers = new WebGPUObjectManager("Sampler", this.mem); + + /** @type {WebGPUObjectManager} */ + this.shaderModules = new WebGPUObjectManager("ShaderModule", this.mem); + + /** @type {WebGPUObjectManager} */ + this.surfaces = new WebGPUObjectManager("Surface", this.mem); + + /** @type {WebGPUObjectManager} */ + this.textures = new WebGPUObjectManager("Texture", this.mem); + + /** @type {WebGPUObjectManager} */ + this.textureViews = new WebGPUObjectManager("TextureView", this.mem); + } + + /** + * @param {number|BigInt} src + * @returns {number|BigInt} + */ + uint(src) { + if (this.mem.intSize == 8) { + return BigInt(src); + } else if (this.mem.intSize == 4) { + return src; + } else { + throw new Error("unreachable"); + } + } + + /** + * @param {number|BigInt} src + * @returns {number} + */ + unwrapBigInt(src) { + if (typeof src == "number") { + return src; + } + + const MAX_SAFE_INTEGER = 9007199254740991n; + if (typeof src != "bigint") { + throw new TypeError(`unwrapBigInt got invalid param of type ${typeof src}`); + } + + if (src > MAX_SAFE_INTEGER) { + throw new Error(`unwrapBigInt precision would be lost converting ${src}`); + } + + return Number(src); + } + + /** + * @param {boolean} condition + * @param {string} message + */ + assert(condition, message = "assertion failure") { + if (!condition) { + throw new Error(message); + } + } + + /** + * @template T + * + * @param {number} count + * @param {number} start + * @param {function(number): T} decoder + * @param {number} stride + * @returns {Array} + */ + array(count, start, decoder, stride) { + if (count == 0) { + return []; + } + this.assert(start != 0); + + const out = []; + for (let i = 0; i < count; i += 1) { + out.push(decoder.call(this, start)); + start += stride; + } + return out; + } + + /** + * @param {string} name + * @param {number} ptr + * @returns {`GPU${name}`} + */ + enumeration(name, ptr) { + const int = this.mem.loadI32(ptr); + this.assert(this.enums[name], `Unknown enumeration "${name}"`); + return this.enums[name][int]; + } + + /** + * @param {GPUSupportedFeatures} features + * @param {number} ptr + * @returns {BigInt|number} + */ + genericEnumerateFeatures(features, ptr) { + const availableFeatures = []; + this.enums.FeatureName.forEach((feature, value) => { + if (!feature) { + return; + } + + if (features.has(feature)) { + availableFeatures.push(value); + } + }); + + if (ptr != 0) { + for (let i = 0; i < availableFeatures.length; i += 1) { + this.mem.storeI32(ptr + (i * 4), availableFeatures[i]); + } + } + + return this.uint(availableFeatures.length); + } + + /** + * @param {GPUSupportedLimits} limits + * @param {number} ptr + */ + genericGetLimits(limits, supportedLimitsPtr) { + this.assert(supportedLimitsPtr != 0); + const limitsPtr = supportedLimitsPtr + 8; + + this.mem.storeU32(limitsPtr + 0, limits.maxTextureDimension1D); + this.mem.storeU32(limitsPtr + 4, limits.maxTextureDimension2D); + this.mem.storeU32(limitsPtr + 8, limits.maxTextureDimension3D); + this.mem.storeU32(limitsPtr + 12, limits.maxTextureArrayLayers); + this.mem.storeU32(limitsPtr + 16, limits.maxBindGroups); + this.mem.storeU32(limitsPtr + 20, limits.maxBindGroupsPlusVertexBuffers); + this.mem.storeU32(limitsPtr + 24, limits.maxBindingsPerBindGroup); + this.mem.storeU32(limitsPtr + 28, limits.maxDynamicUniformBuffersPerPipelineLayout); + this.mem.storeU32(limitsPtr + 32, limits.maxDynamicStorageBuffersPerPipelineLayout); + this.mem.storeU32(limitsPtr + 36, limits.maxSampledTexturesPerShaderStage); + this.mem.storeU32(limitsPtr + 40, limits.maxSamplersPerShaderStage); + this.mem.storeU32(limitsPtr + 44, limits.maxStorageBuffersPerShaderStage); + this.mem.storeU32(limitsPtr + 48, limits.maxStorageTexturesPerShaderStage); + this.mem.storeU32(limitsPtr + 52, limits.maxUniformBuffersPerShaderStage); + this.mem.storeU64(limitsPtr + 56, limits.maxUniformBufferBindingSize); + this.mem.storeU64(limitsPtr + 64, limits.maxStorageBufferBindingSize); + this.mem.storeU32(limitsPtr + 72, limits.minUniformBufferOffsetAlignment); + this.mem.storeU32(limitsPtr + 76, limits.minStorageBufferOffsetAlignment); + this.mem.storeU32(limitsPtr + 80, limits.maxVertexBuffers); + this.mem.storeU64(limitsPtr + 88, limits.maxBufferSize); + this.mem.storeU32(limitsPtr + 96, limits.maxVertexAttributes); + this.mem.storeU32(limitsPtr + 100, limits.maxVertexBufferArrayStride); + this.mem.storeU32(limitsPtr + 104, limits.maxInterStageShaderComponents); + this.mem.storeU32(limitsPtr + 108, limits.maxInterStageShaderVariables); + this.mem.storeU32(limitsPtr + 112, limits.maxColorAttachments); + this.mem.storeU32(limitsPtr + 116, limits.maxColorAttachmentBytesPerSample); + this.mem.storeU32(limitsPtr + 120, limits.maxComputeWorkgroupStorageSize); + this.mem.storeU32(limitsPtr + 124, limits.maxComputeInvocationsPerWorkgroup); + this.mem.storeU32(limitsPtr + 128, limits.maxComputeWorkgroupSizeX); + this.mem.storeU32(limitsPtr + 132, limits.maxComputeWorkgroupSizeY); + this.mem.storeU32(limitsPtr + 136, limits.maxComputeWorkgroupSizeZ); + this.mem.storeU32(limitsPtr + 140, limits.maxComputeWorkgroupsPerDimension); + + return true; + } + + /** + * @param {number} ptr + * @returns {GPUFeatureName} + */ + FeatureNamePtr(ptr) { + return this.FeatureName(this.mem.loadI32(ptr)); + } + + /** + * @param {number} featureInt + * @returns {GPUFeatureName} + */ + FeatureName(featureInt) { + return this.enums.FeatureName[featureInt]; + } + + /** + * @param {number} ptr + * @returns {GPUSupportedLimits} + */ + RequiredLimitsPtr(ptr) { + const start = this.mem.loadPtr(ptr); + if (start == 0) { + return undefined; + } + + return this.Limits(start + 8); + } + + /** + * @param {number} start + * @return {GPUSupportedLimits} + */ + Limits(start) { + const limitU32 = (ptr) => { + const value = this.mem.loadU32(ptr); + if (value == 0xFFFFFFFF) { // LIMIT_32_UNDEFINED. + return undefined; + } + return value; + }; + + const limitU64 = (ptr) => { + const part1 = this.mem.loadU32(ptr); + const part2 = this.mem.loadU32(ptr + 4); + if (part1 != 0xFFFFFFFF || part2 != 0xFFFFFFFF) { // LIMIT_64_UNDEFINED. + return this.mem.loadU64(ptr); + } + return undefined; + }; + + return { + maxTextureDimension1D: limitU32(start + 0), + maxTextureDimension2D: limitU32(start + 4), + maxTextureDimension3D: limitU32(start + 8), + maxTextureArrayLayers: limitU32(start + 12), + maxBindGroups: limitU32(start + 16), + maxBindGroupsPlusVertexBuffers: limitU32(start + 20), + maxBindingsPerBindGroup: limitU32(start + 24), + maxDynamicUniformBuffersPerPipelineLayout: limitU32(start + 28), + maxDynamicStorageBuffersPerPipelineLayout: limitU32(start + 32), + maxSampledTexturesPerShaderStage: limitU32(start + 36), + maxSamplersPerShaderStage: limitU32(start + 40), + maxStorageBuffersPerShaderStage: limitU32(start + 44), + maxStorageTexturesPerShaderStage: limitU32(start + 48), + maxUniformBuffersPerShaderStage: limitU32(start + 52), + maxUniformBufferBindingSize: limitU64(start + 56), + maxStorageBufferBindingSize: limitU64(start + 64), + minUniformBufferOffsetAlignment: limitU32(start + 72), + minStorageBufferOffsetAlignment: limitU32(start + 76), + maxVertexBuffers: limitU32(start + 80), + maxBufferSize: limitU64(start + 88), + maxVertexAttributes: limitU32(start + 96), + maxVertexBufferArrayStride: limitU32(start + 100), + maxInterStageShaderComponents: limitU32(start + 104), + maxInterStageShaderVariables: limitU32(start + 108), + maxColorAttachments: limitU32(start + 112), + maxColorAttachmentBytesPerSample: limitU32(start + 116), + maxComputeWorkgroupStorageSize: limitU32(start + 120), + maxComputeInvocationsPerWorkgroup: limitU32(start + 124), + maxComputeWorkgroupSizeX: limitU32(start + 128), + maxComputeWorkgroupSizeY: limitU32(start + 132), + maxComputeWorkgroupSizeZ: limitU32(start + 136), + maxComputeWorkgroupsPerDimension: limitU32(start + 140), + }; + } + + /** + * @param {number} start + * @returns {GPUQueueDescriptor} + */ + QueueDescriptor(start) { + return { + label: this.mem.loadCstring(start + 4), + }; + } + + /** + * @param {number} ptr + * @returns {GPUComputePassTimestampWrites} + */ + ComputePassTimestampWritesPtr(ptr) { + const start = this.mem.loadPtr(ptr); + if (start == 0) { + return undefined; + } + + return { + querySet: this.querySets.get(this.mem.loadPtr(start + 0)), + beginningOfPassWriteIndex: this.mem.loadU32(start + 4), + endOfPassWriteIndex: this.mem.loadU32(start + 8), + }; + } + + /** + * @param {number} start + * @returns {GPURenderPassColorAttachment} + */ + RenderPassColorAttachment(start) { + const viewIdx = this.mem.loadPtr(start + 4); + const resolveTargetIdx = this.mem.loadPtr(start + 8); + + 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), + clearValue: this.Color(start + 24), + }; + } + + /** + * @param {number} start + * @returns {GPUColor} + */ + Color(start) { + return { + r: this.mem.loadF64(start + 0), + g: this.mem.loadF64(start + 8), + b: this.mem.loadF64(start + 16), + a: this.mem.loadF64(start + 24), + }; + } + + /** + * @param {number} ptr + * @returns {GPURenderPassDepthStencilAttachment} + */ + RenderPassDepthStencilAttachmentPtr(ptr) { + const start = this.mem.loadPtr(ptr); + if (start == 0) { + return undefined; + } + + return { + view: this.textureViews.get(this.mem.loadPtr(start + 0)), + depthLoadOp: this.enumeration("LoadOp", start + 4), + depthStoreOp: this.enumeration("StoreOp", start + 8), + depthClearValue: this.mem.loadF32(start + 12), + depthReadOnly: this.mem.loadB32(start + 16), + stencilLoadOp: this.enumeration("LoadOp", start + 20), + stencilStoreOp: this.enumeration("StoreOp", start + 24), + stencilClearValue: this.mem.loadF32(start + 28), + stencilReadOnly: this.mem.loadB32(start + 32), + }; + } + + /** + * @param {number} ptr + * @returns {undefined|GPUQuerySet} + */ + QuerySet(ptr) { + ptr = this.mem.loadPtr(ptr); + if (ptr == 0) { + return undefined; + } + + return this.querySets.get(ptr); + } + + /** + * @param {number} ptr + * @returns {GPURenderPassTimestampWrites} + */ + RenderPassTimestampWritesPtr(ptr) { + return this.ComputePassTimestampWritesPtr(ptr); + } + + /** + * @param {number} start + * @returns {GPUImageDataLayout} + */ + TextureDataLayout(start) { + return { + offset: this.mem.loadU64(start + 8), + bytesPerRow: this.mem.loadU32(start + 16), + rowsPerImage: this.mem.loadU32(start + 20), + }; + } + + /** + * @param {number} start + * @returns {GPUImageCopyBuffer} + */ + ImageCopyBuffer(start) { + return { + ...this.TextureDataLayout(start + 8), + buffer: this.buffers.get(this.mem.loadPtr(start + 32)).buffer, + }; + } + + /** + * @param {number} start + * @returns {GPUImageCopyTexture} + */ + ImageCopyTexture(start) { + return { + texture: this.textures.get(this.mem.loadPtr(start + 4)), + mipLevel: this.mem.loadU32(start + 8), + origin: this.Origin3D(start + 12), + aspect: this.enumeration("TextureAspect", start + 24), + }; + } + + /** + * @param {number} start + * @returns {GPUOrigin3D} + */ + Origin3D(start) { + return { + x: this.mem.loadU32(start + 0), + y: this.mem.loadU32(start + 4), + z: this.mem.loadU32(start + 8), + }; + } + + /** + * @param {number} start + * @returns {GPUExtent3D} + */ + Extent3D(start) { + return { + width: this.mem.loadU32(start + 0), + height: this.mem.loadU32(start + 4), + depthOrArrayLayers: this.mem.loadU32(start + 8), + }; + } + + /** + * @param {number} start + * @returns {GPUBindGroupEntry} + */ + BindGroupEntry(start) { + const buffer = this.mem.loadPtr(start + 8); + const sampler = this.mem.loadPtr(start + 32); + const textureView = this.mem.loadPtr(start + 36); + + /** @type {GPUBindingResource} */ + let resource; + if (buffer > 0) { + resource = { + buffer: this.buffers.get(buffer).buffer, + offset: this.mem.loadU64(start + 16), + size: this.mem.loadU64(start + 24), + } + } else if (sampler > 0) { + resource = this.samplers.get(sampler); + } else if (textureView > 0) { + resource = this.textureViews.get(textureView); + } + + return { + binding: this.mem.loadU32(start + 4), + resource: resource, + }; + } + + /** + * @param {number} start + * @returns {GPUBindGroupLayoutEntry} + */ + BindGroupLayoutEntry(start) { + const entry = { + binding: this.mem.loadU32(start + 4), + visibility: this.mem.loadU32(start + 8), + buffer: this.BufferBindingLayout(start + 16), + sampler: this.SamplerBindingLayout(start + 40), + texture: this.TextureBindingLayout(start + 48), + storageTexture: this.StorageTextureBindingLayout(start + 64), + }; + if (!entry.buffer.type) { + entry.buffer = undefined; + } + if (!entry.sampler.type) { + entry.sampler = undefined; + } + if (!entry.texture.sampleType) { + entry.texture = undefined; + } + if (!entry.storageTexture.access) { + entry.storageTexture = undefined; + } + return entry; + } + + /** + * @param {number} start + * @returns {GPUBufferBindingLayout} + */ + BufferBindingLayout(start) { + return { + type: this.enumeration("BufferBindingType", start + 4), + hasDynamicOffset: this.mem.loadB32(start + 8), + minBindingSize: this.mem.loadU64(start + 16), + }; + } + + /** + * @param {number} start + * @returns {GPUSamplerBindingLayout} + */ + SamplerBindingLayout(start) { + return { + type: this.enumeration("SamplerBindingType", start + 4), + }; + } + + /** + * @param {number} start + * @returns {GPUTextureBindingLayout} + */ + TextureBindingLayout(start) { + return { + sampleType: this.enumeration("TextureSampleType", start + 4), + viewDimension: this.enumeration("TextureViewDimension", start + 8), + multisampled: this.mem.loadB32(start + 12), + }; + } + + /** + * @param {number} start + * @returns {GPUStorageTextureBindingLayout} + */ + StorageTextureBindingLayout(start) { + return { + access: this.enumeration("StorageTextureAccess", start + 4), + format: this.enumeration("TextureFormat", start + 8), + viewDimension: this.enumeration("TextureViewDimension", start + 12), + }; + } + + /** + * @param {number} start + * @returns {GPUProgrammableStage} + */ + ProgrammableStageDescriptor(start) { + const constantsArray = this.array( + this.mem.loadUint(start + 8 + this.mem.intSize), + this.mem.loadPtr(start + 8 + this.mem.intSize*2), + this.ConstantEntry, + 16, + ); + return { + module: this.shaderModules.get(this.mem.loadPtr(start + 4)), + entryPoint: this.mem.loadCstring(start + 8), + constants: constantsArray.reduce((prev, curr) => { + prev[curr.key] = curr.value; + return prev; + }, {}), + }; + } + + /** + * @param {number} start + * @returns {{ key: string, value: number }} + */ + ConstantEntry(start) { + return { + key: this.mem.loadCstring(start + 4), + value: this.mem.loadF64(start + 8), + }; + } + + /** + * @param {number} start + * @returns {GPUComputePipelineDescriptor} + */ + ComputePipelineDescriptor(start) { + const layoutIdx = this.mem.loadPtr(start + 8) + return { + label: this.mem.loadCstring(start + 4), + layout: layoutIdx > 0 ? this.pipelineLayouts.get(layoutIdx) : undefined, + compute: this.ProgrammableStageDescriptor(start + 8 + this.mem.intSize), + }; + } + + /** + * @param {number} start + * @returns {GPUVertexState} + */ + VertexState(start) { + let off = 8 + this.mem.intSize; + const constantsArray = this.array( + this.mem.loadUint(start + off), + this.mem.loadPtr(start + off + this.mem.intSize), + this.ConstantEntry, + 16, + ); + + off += this.mem.intSize * 2; + + return { + module: this.shaderModules.get(this.mem.loadPtr(start + 4)), + entryPoint: this.mem.loadCstring(start + 8), + constants: constantsArray.reduce((prev, curr) => { + prev[curr.key] = curr.value; + return prev; + }, {}), + buffers: this.array( + this.mem.loadUint(start + off), + this.mem.loadPtr(start + off + this.mem.intSize), + this.VertexBufferLayout, + this.mem.intSize == 8 ? 32 : 24, + ), + }; + } + + /** + * @param {number} start + * @returns {?GPUVertexBufferLayout} + */ + VertexBufferLayout(start) { + const stepMode = this.enumeration("VertexStepMode", start + 8); + if (stepMode == "vertex-buffer-not-used") { + return null; + } + return { + arrayStride: this.mem.loadU64(start + 0), + stepMode: stepMode, + attributes: this.array( + this.mem.loadUint(start + 8 + this.mem.intSize), + this.mem.loadPtr(start + 8 + this.mem.intSize*2), + this.VertexAttribute, + 24, + ), + }; + } + + /** + * @param {number} start + * @returns {GPUVertexAttribute} + */ + VertexAttribute(start) { + return { + format: this.enumeration("VertexFormat", start + 0), + offset: this.mem.loadU64(start + 8), + shaderLocation: this.mem.loadU32(start + 16), + }; + } + + /** + * @param {number} start + * @returns {GPUPrimitiveState} + */ + PrimitiveState(start) { + let unclippedDepth = undefined; + const nextInChain = this.mem.loadPtr(start); + if (nextInChain != 0) { + const nextInChainType = this.mem.loadI32(nextInChain + 4); + // PrimitiveDepthClipControl = 0x00000007, + if (nextInChainType == 7) { + unclippedDepth = this.mem.loadB32(nextInChain + 8); + } + } + + return { + topology: this.enumeration("PrimitiveTopology", start + 4), + stripIndexFormat: this.enumeration("IndexFormat", start + 8), + frontFace: this.enumeration("FrontFace", start + 12), + cullMode: this.enumeration("CullMode", start + 16), + unclippedDepth: unclippedDepth, + }; + } + + /** + * @param {number} start + * @returns {GPURenderPipelineDescriptor} + */ + RenderPipelineDescriptor(start) { + const layoutIdx = this.mem.loadPtr(start + 8); + const offs = this.mem.intSize == 8 ? [64, 84, 88, 104] : [40, 60, 64, 80]; + return { + label: this.mem.loadCstring(start + 4), + layout: layoutIdx > 0 ? this.pipelineLayouts.get(layoutIdx) : undefined, + vertex: this.VertexState(start + 8 + this.mem.intSize), + primitive: this.PrimitiveState(start + offs[0]), + depthStencil: this.DepthStencilStatePtr(start + offs[1]), + multisample: this.MultisampleState(start + offs[2]), + fragment: this.FragmentStatePtr(start + offs[3]), + }; + } + + /** + * @param {number} start + * @returns {GPUShaderModuleCompilationHint} + */ + ShaderModuleCompilationHint(start) { + return { + entryPoint: this.mem.loadCstring(start + 4), + layout: this.pipelineLayouts.get(this.mem.loadPtr(start + 8)), + }; + } + + /** + * @param {number} ptr + * @returns {?GPUDepthStencilState} + */ + DepthStencilStatePtr(ptr) { + const start = this.mem.loadPtr(ptr); + if (start == 0) { + return undefined; + } + + return { + format: this.enumeration("TextureFormat", start + 4), + depthWriteEnabled: this.mem.loadB32(start + 8), + depthCompare: this.enumeration("CompareFunction", start + 12), + stencilFront: this.StencilFaceState(start + 16), + stencilBack: this.StencilFaceState(start + 32), + stencilReadMask: this.mem.loadU32(start + 48), + stencilWriteMask: this.mem.loadU32(start + 52), + depthBias: this.mem.loadI32(start + 56), + depthBiasSlopeScale: this.mem.loadF32(start + 60), + depthBiasClamp: this.mem.loadF32(start + 64), + }; + } + + /** + * @param {number} start + * @returns {GPUStencilFaceState} + */ + StencilFaceState(start) { + return { + compare: this.enumeration("CompareFunction", start + 0), + failOp: this.enumeration("StencilOperation", start + 4), + depthFailOp: this.enumeration("StencilOperation", start + 8), + passOp: this.enumeration("StencilOperation", start + 12), + }; + } + + /** + * @param {number} start + * @returns {GPUMultisampleState} + */ + MultisampleState(start) { + return { + count: this.mem.loadU32(start + 4), + mask: this.mem.loadU32(start + 8), + alphaToCoverageEnabled: this.mem.loadB32(start + 12), + }; + } + + /** + * @param {number} ptr + * @returns {?GPUFragmentState} + */ + FragmentStatePtr(ptr) { + const start = this.mem.loadPtr(ptr); + if (start == 0) { + return undefined; + } + + let off = 8 + this.mem.intSize; + + const constantsArray = this.array( + this.mem.loadUint(start + off), + this.mem.loadPtr(start + off + this.mem.intSize), + this.ConstantEntry, + 16, + ); + + off += this.mem.intSize * 2; + + return { + module: this.shaderModules.get(this.mem.loadPtr(start + 4)), + entryPoint: this.mem.loadCstring(start + 8), + constants: constantsArray.reduce((prev, curr) => { + prev[curr.key] = curr.value; + return prev; + }, {}), + targets: this.array( + this.mem.loadUint(start + off), + this.mem.loadPtr(start + off + this.mem.intSize), + this.ColorTargetState, + 16, + ), + }; + } + + /** + * @param {number} start + * @returns {GPUColorTargetState} + */ + ColorTargetState(start) { + return { + format: this.enumeration("TextureFormat", start + 4), + blend: this.BlendStatePtr(start + 8), + writeMask: this.mem.loadU32(start + 12), + }; + } + + /** + * @param {number} ptr + * @returns {?GPUBlendState} + */ + BlendStatePtr(ptr) { + const start = this.mem.loadPtr(ptr); + if (start == 0) { + return undefined; + } + + return { + color: this.BlendComponent(start + 0), + alpha: this.BlendComponent(start + 12), + }; + } + + /** + * @param {number} start + * @returns {?GPUBlendComponent} + */ + BlendComponent(start) { + return { + operation: this.enumeration("BlendOperation", start + 0), + srcFactor: this.enumeration("BlendFactor", start + 4), + dstFactor: this.enumeration("BlendFactor", start + 8), + }; + } + + getInterface() { + return { + /** + * @param {0|number} descriptorPtr + * @returns {number} + */ + wgpuCreateInstance: (descriptorPtr) => { + if (!navigator.gpu) { + console.error("WebGPU is not supported by this browser"); + return 0; + } + + return this.instances.create({}); + }, + + /** + * @param {number} deviceIdx + * @param {number} procNamePtr + * @returns {number} + */ + wgpuGetProcAddress: (deviceIdx, procNamePtr) => { + console.error(`unimplemented: wgpuGetProcAddress`); + return 0; + }, + + /* ---------------------- Adapter ---------------------- */ + + /** + * @param {number} adapterIdx + * @param {number} featuresPtr + * @returns {number|BigInt} + */ + wgpuAdapterEnumerateFeatures: (adapterIdx, featuresPtr) => { + const adapter = this.adapters.get(adapterIdx); + return this.genericEnumerateFeatures(adapter.features, featuresPtr); + }, + + /** + * @param {number} adapterIdx + * @param {number} supportedLimitsPtr + * @returns {boolean} + */ + wgpuAdapterGetLimits: (adapterIdx, supportedLimitsPtr) => { + const adapter = this.adapters.get(adapterIdx); + return this.genericGetLimits(adapter.limits, supportedLimitsPtr); + }, + + /** + * @param {number} adapterIdx + * @param {number} propertiesPtr + */ + wgpuAdapterGetProperties: (adapterIdx, propertiesPtr) => { + this.assert(propertiesPtr != 0); + // Unknown adapter. + this.mem.storeI32(propertiesPtr + 28, 3); + // WebGPU backend. + this.mem.storeI32(propertiesPtr + 32, 2); + }, + + /** + * @param {number} adapterIdx + * @param {number} featureInt + * @returns {boolean} + */ + wgpuAdapterHasFeature: (adapterIdx, featureInt) => { + const adapter = this.adapters.get(adapterIdx); + 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 + * @param {number} callbackPtr + * @param {0|number} userdata + */ + wgpuAdapterRequestDevice: async (adapterIdx, descriptorPtr, callbackPtr, userdata) => { + const adapter = this.adapters.get(adapterIdx); + const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); + + /** @type {GPUDeviceDescriptor} */ + let descriptor; + if (descriptorPtr != 0) { + descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + requiredFeatures: this.array( + this.mem.loadUint(descriptorPtr + 8), + this.mem.loadPtr(descriptorPtr + 8 + this.mem.intSize), + this.FeatureNamePtr, + 4, + ), + requiredLimits: this.RequiredLimitsPtr(descriptorPtr + 8 + this.mem.intSize + 4), + defaultQueue: this.QueueDescriptor( descriptorPtr + 8 + this.mem.intSize + 4 + 4), + }; + } + + let deviceIdx; + try { + const 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); + } + + callback(0, deviceIdx, null, userdata); + }, + + ...this.adapters.interface(), + + /* ---------------------- BindGroup ---------------------- */ + + ...this.bindGroups.interface(true), + + /* ---------------------- BindGroupLayout ---------------------- */ + + ...this.bindGroupLayouts.interface(true), + + /* ---------------------- Buffer ---------------------- */ + + /** @param {number} bufferIdx */ + wgpuBufferDestroy: (bufferIdx) => { + const buffer = this.buffers.get(bufferIdx); + buffer.buffer.destroy(); + }, + + /** + * @param {number} bufferIdx + * @param {number|BigInt} offset + * @param {number|BigInt} size + * @returns {number} + */ + wgpuBufferGetMappedRange: (bufferIdx, offset, size) => { + const buffer = this.buffers.get(bufferIdx); + offset = this.unwrapBigInt(offset); + size = this.unwrapBigInt(size); + + this.assert(!buffer.mapping, "buffer already mapped"); + + const range = buffer.buffer.getMappedRange(offset, size); + + const ptr = this.mem.exports.wgpu_alloc(range.byteLength); + + buffer.mapping = { range: range, ptr: ptr, size: range.byteLength }; + return ptr; + }, + + /** + * @param {number} bufferIdx + * @returns {BigInt} + */ + wgpuBufferGetSize: (bufferIdx) => { + const buffer = this.buffers.get(bufferIdx); + return BigInt(buffer.buffer.size); + }, + + /** + * @param {number} bufferIdx + * @returns {number} + */ + wgpuBufferGetUsage: (bufferIdx) => { + const buffer = this.buffers.get(bufferIdx); + return buffer.buffer.usage; + }, + + /** + * @param {number} bufferIdx + * @param {number} mode + * @param {number|BigInt} offset + * @param {number|BigInt} size + * @param {number} callbackPtr + * @param {0|number} userdata + */ + wgpuBufferMapAsync: async (bufferIdx, mode, offset, size, callbackPtr, userdata) => { + const buffer = this.buffers.get(bufferIdx); + const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); + offset = this.unwrapBigInt(offset); + size = this.unwrapBigInt(size); + + if (buffer.buffer.mapState == "pending") { + callback(this.enums.BufferMapAsyncStatus.MappingAlreadyPending, userdata); + } else { + let result; + try { + await buffer.buffer.mapAsync(mode, offset, size); + result = 0; // Success. + } catch(e) { + console.warn(e); + result = 2; // Unknown error. + + if (e instanceof DomException) { + if (e.name == "OperationError") { + result = 1; // Validation error. + } + } + } + + callback(result, userdata); + } + }, + + /** + * @param {number} bufferIdx + * @param {number} labelPtr + */ + wgpuBufferSetLabel: (bufferIdx, labelPtr) => { + const buffer = this.buffers.get(bufferIdx); + buffer.buffer.label = this.mem.loadCstring(labelPtr); + }, + + /** + * @param {number} bufferIdx + */ + wgpuBufferUnmap: (bufferIdx) => { + const buffer = this.buffers.get(bufferIdx); + this.assert(buffer.mapping, "buffer not mapped"); + + const mapping = new Uint8Array(this.mem.memory.buffer, buffer.mapping.ptr, buffer.mapping.size); + (new Uint8Array(buffer.mapping.range)).set(mapping); + + buffer.buffer.unmap(); + + this.mem.exports.wgpu_free(buffer.mapping.ptr); + buffer.mapping = null; + }, + + ...this.buffers.interface(), + + /* ---------------------- CommandBuffer ---------------------- */ + + ...this.commandBuffers.interface(true), + + /* ---------------------- CommandEncoder ---------------------- */ + + /** + * @param {number} commandEncoderIdx + * @param {0|number} descriptorPtr + * @return {number} The compute pass encoder + */ + wgpuCommandEncoderBeginComputePass: (commandEncoderIdx, descriptorPtr) => { + const commandEncoder = this.commandEncoders.get(commandEncoderIdx); + + /** @type {?GPUComputePassDescriptor} */ + let descriptor; + if (descriptorPtr != 0) { + descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + timestampWrites: this.ComputePassTimestampWritesPtr(descriptorPtr + 8), + }; + } + + const computePassEncoder = commandEncoder.beginComputePass(descriptor); + return this.computePassEncoders.create(computePassEncoder); + }, + + /** + * @param {number} commandEncoderIdx + * @param {number} descriptorPtr + * @return {number} The render pass encoder + */ + wgpuCommandEncoderBeginRenderPass: (commandEncoderIdx, descriptorPtr) => { + const commandEncoder = this.commandEncoders.get(commandEncoderIdx); + this.assert(descriptorPtr != 0); + + let maxDrawCount = undefined; + const nextInChain = this.mem.loadPtr(descriptorPtr); + if (nextInChain != 0) { + const nextInChainType = this.mem.loadI32(nextInChain + 4); + // RenderPassDescriptorMaxDrawCount = 0x0000000F, + if (nextInChainType == 0x0000000F) { + maxDrawCount = this.mem.loadU64(nextInChain + 8); + } + } + + /** @type {GPURenderPassDescriptor} */ + const descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + colorAttachments: this.array( + this.mem.loadUint(descriptorPtr + 8), + this.mem.loadPtr(descriptorPtr + 8 + this.mem.intSize), + this.RenderPassColorAttachment, + 56, + ), + depthStencilAttachment: this.RenderPassDepthStencilAttachmentPtr(descriptorPtr + 8 + this.mem.intSize + 4), + occlusionQuerySet: this.QuerySet(descriptorPtr + 8 + this.mem.intSize + 4 + 4), + timestampWrites: this.RenderPassTimestampWritesPtr(descriptorPtr + 8 + this.mem.intSize + 4 + 4), + maxDrawCount: maxDrawCount, + }; + + const renderPassEncoder = commandEncoder.beginRenderPass(descriptor); + return this.renderPassEncoders.create(renderPassEncoder); + }, + + /** + * @param {number} commandEncoderIdx + * @param {number} bufferIdx + * @param {BigInt} offset + * @param {BigInt} size + */ + wgpuCommandEncoderClearBuffer: (commandEncoderIdx, bufferIdx, offset, size) => { + const commandEncoder = this.commandEncoders.get(commandEncoderIdx); + const buffer = this.buffers.get(bufferIdx); + offset = this.unwrapBigInt(offset); + size = this.unwrapBigInt(size); + commandEncoder.clearBuffer(buffer.buffer, offset, size); + }, + + /** + * @param {number} commandEncoderIdx + * @param {number} sourceIdx + * @param {BigInt} sourceOffset + * @param {number} destinationIdx + * @param {BigInt} destinationOffset + * @param {BigInt} size + */ + wgpuCommandEncoderCopyBufferToBuffer: (commandEncoderIdx, sourceIdx, sourceOffset, destinationIdx, destinationOffset, size) => { + const commandEncoder = this.commandEncoders.get(commandEncoderIdx); + const source = this.buffers.get(sourceIdx); + const destination = this.buffers.get(destinationIdx); + sourceOffset = this.unwrapBigInt(sourceOffset); + destinationOffset = this.unwrapBigInt(destinationOffset); + size = this.unwrapBigInt(size); + commandEncoder.copyBufferToBuffer(source.buffer, sourceOffset, destination.buffer, destinationOffset, size); + }, + + /** + * @param {number} commandEncoderIdx + * @param {number} sourcePtr + * @param {number} destinationPtr + * @param {number} copySizePtr + */ + wgpuCommandEncoderCopyBufferToTexture: (commandEncoderIdx, sourcePtr, destinationPtr, copySizePtr) => { + const commandEncoder = this.commandEncoders.get(commandEncoderIdx); + commandEncoder.copyBufferToTexture( + this.ImageCopyBuffer(sourcePtr), + this.ImageCopyTexture(destinationPtr), + this.Extent3D(copySizePtr), + ); + }, + + /** + * @param {number} commandEncoderIdx + * @param {number} sourcePtr + * @param {number} destinationPtr + * @param {number} copySizePtr + */ + wgpuCommandEncoderCopyTextureToBuffer: (commandEncoderIdx, sourcePtr, destinationPtr, copySizePtr) => { + const commandEncoder = this.commandEncoders.get(commandEncoderIdx); + commandEncoder.copyTextureToBuffer( + this.ImageCopyTexture(sourcePtr), + this.ImageCopyBuffer(destinationPtr), + this.Extent3D(copySizePtr), + ); + }, + + /** + * @param {number} commandEncoderIdx + * @param {number} sourcePtr + * @param {number} destinationPtr + * @param {number} copySizePtr + */ + wgpuCommandEncoderCopyTextureToTexture: (commandEncoderIdx, sourcePtr, destinationPtr, copySizePtr) => { + const commandEncoder = this.commandEncoders.get(commandEncoderIdx); + commandEncoder.copyTextureToTexture( + this.ImageCopyTexture(sourcePtr), + this.ImageCopyTexture(destinationPtr), + this.Extent3D(copySizePtr), + ); + }, + + /** + * @param {number} commandEncoderIdx + * @param {0|number} descriptorPtr + * @returns {number} The command buffer. + */ + wgpuCommandEncoderFinish: (commandEncoderIdx, descriptorPtr) => { + const commandEncoder = this.commandEncoders.get(commandEncoderIdx); + + /** @type {undefined|GPUCommandBufferDescriptor} */ + let descriptor; + if (descriptorPtr != 0) { + descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + }; + } + + const commandBuffer = commandEncoder.finish(descriptor); + return this.commandBuffers.create(commandBuffer); + }, + + /** + * @param {number} commandEncoderIdx + * @param {number} markerLabelPtr + */ + wgpuCommandEncoderInsertDebugMarker: (commandEncoderIdx, markerLabelPtr) => { + const commandEncoder = this.commandEncoders.get(commandEncoderIdx); + commandEncoder.insertDebugMarker(this.mem.loadCstring(markerLabelPtr)); + }, + + /** + * @param {number} commandEncoderIdx + */ + wgpuCommandEncoderPopDebugGroup: (commandEncoderIdx) => { + const commandEncoder = this.commandEncoders.get(commandEncoderIdx); + commandEncoder.popDebugGroup(); + }, + + /** + * @param {number} commandEncoderIdx + * @param {number} markerLabelPtr + */ + wgpuCommandEncoderPushDebugGroup: (commandEncoderIdx, groupLabelPtr) => { + const commandEncoder = this.commandEncoders.get(commandEncoderIdx); + commandEncoder.pushDebugGroup(this.mem.loadCstring(groupLabelPtr)); + }, + + /** + * @param {number} commandEncoderIdx + * @param {number} querySetIdx + * @param {number} firstQuery + * @param {number} queryCount + * @param {number} destinationIdx + * @param {BigInt} destinationOffset + */ + wgpuCommandEncoderResolveQuerySet: (commandEncoderIdx, querySetIdx, firstQuery, queryCount, destinationIdx, destinationOffset) => { + const commandEncoder = this.commandEncoders.get(commandEncoderIdx); + const querySet = this.querySets.get(querySetIdx); + const destination = this.buffers.get(destinationIdx); + destinationOffset = this.unwrapBigInt(destinationOffset); + commandEncoder.resolveQuerySet(querySet, firstQuery, queryCount, destination.buffer, destinationOffset); + }, + + /** + * @param {number} commandEncoderIdx + * @param {number} querySetIdx + * @param {number} queryIndex + */ + wgpuCommandEncoderWriteTimestamp: (commandEncoderIdx, querySetIdx, queryIndex) => { + const commandEncoder = this.commandEncoders.get(commandEncoderIdx); + const querySet = this.querySets.get(querySetIdx); + commandEncoder.writeTimestamp(querySet, queryIndex); + }, + + ...this.commandEncoders.interface(true), + + /* ---------------------- ComputePassEncoder ---------------------- */ + + + /** + * @param {number} computePassEncoderIdx + * @param {number} workgroupCountX + * @param {number} workgroupCountY + * @param {number} workgroupCountZ + */ + wgpuComputePassEncoderDispachWorkgroups: (computePassEncoderIdx, workgroupCountX, workgroupCountY, workgroupCountZ) => { + const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); + computePassEncoder.dispatchWorkgroups(workgroupCountX, workgroupCountY, workgroupCountZ); + }, + + /** + * @param {number} computePassEncoderIdx + * @param {number} indirectBufferIdx + * @param {BigInt} indirectOffset + */ + wgpuComputePassEncoderDispachWorkgroupsIndirect: (computePassEncoderIdx, indirectBufferIdx, indirectOffset) => { + const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); + const indirectBuffer = this.buffers.get(indirectBufferIdx); + indirectOffset = this.unwrapBigInt(indirectOffset); + computePassEncoder.dispatchWorkgroupsIndirect(indirectBuffer.buffer, indirectOffset); + }, + + /** + * @param {number} computePassEncoderIdx + */ + wgpuComputePassEncoderEnd: (computePassEncoderIdx) => { + const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); + computePassEncoder.end(); + }, + + /** + * @param {number} computePassEncoderIdx + * @param {number} markerLabelPtr + */ + wgpuComputePassEncoderInsertDebugMarker: (computePassEncoderIdx, markerLabelPtr) => { + const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); + computePassEncoder.insertDebugMarker(this.mem.loadCstring(markerLabelPtr)); + }, + + /** + * @param {number} computePassEncoderIdx + */ + wgpuComputePassEncoderPopDebugGroup: (computePassEncoderIdx) => { + const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); + computePassEncoder.popDebugGroup(); + }, + + /** + * @param {number} computePassEncoderIdx + * @param {number} markerLabelPtr + */ + wgpuComputePassEncoderPushDebugGroup: (computePassEncoderIdx, groupLabelPtr) => { + const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); + computePassEncoder.pushDebugGroup(this.mem.loadCstring(groupLabelPtr)); + }, + + /** + * @param {number} computePassEncoderIdx + * @param {number} groupIndex + * @param {0|number} groupIdx + * @param {number|BigInt} dynamicOffsetCount + * @param {number} dynamicOffsetsPtr + */ + wgpuComputePassEncoderSetBindGroup: (computePassEncoderIdx, groupIndex, groupIdx, dynamicOffsetCount, dynamicOffsetsPtr) => { + const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); + dynamicOffsetCount = this.unwrapBigInt(dynamicOffsetCount); + + let bindGroup; + if (groupIdx != 0) { + bindGroup = this.bindGroups.get(groupIdx); + } + + const dynamicOffsets = []; + for (let i = 0; i < dynamicOffsetCount; i += 1) { + dynamicOffsets.push(this.mem.loadU32(dynamicOffsetsPtr)); + dynamicOffsetsPtr += 4; + } + + computePassEncoder.setBindGroup(groupIndex, bindGroup, dynamicOffsets); + }, + + /** + * @param {number} computePassEncoderIdx + * @param {number} pipelineIdx + */ + wgpuComputePassEncoderSetPipeline: (computePassEncoderIdx, pipelineIdx) => { + const computePassEncoder = this.computePassEncoders.get(computePassEncoderIdx); + const pipeline = this.computePipelines.get(pipelineIdx); + computePassEncoder.setPipeline(pipeline); + }, + + ...this.computePassEncoders.interface(true), + + /* ---------------------- ComputePipeline ---------------------- */ + + /** + * @param {number} computePipelineIdx + * @param {number} groupIndex + * @returns {number} + */ + wgpuComputePipelineGetBindGroupLayout: (computePipelineIdx, groupIndex) => { + const computePipeline = this.computePipelines.get(computePipelineIdx); + const bindGroupLayout = computePipeline.getBindGroupLayout(groupIndex); + return this.bindGroupLayouts.create(bindGroupLayout); + }, + + ...this.computePipelines.interface(true), + + /* ---------------------- Device ---------------------- */ + + /** + * @param {number} deviceIdx + * @param {number} descriptorPtr + * @returns {number} The bind group. + */ + wgpuDeviceCreateBindGroup: (deviceIdx, descriptorPtr) => { + const device = this.devices.get(deviceIdx); + this.assert(descriptorPtr != 0); + + /** @type {GPUBindGroupDescriptor} */ + const descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + layout: this.bindGroupLayouts.get(this.mem.loadPtr(descriptorPtr + 8)), + entries: this.array( + this.mem.loadUint(descriptorPtr + 8 + this.mem.intSize), + this.mem.loadPtr(descriptorPtr + 8 + this.mem.intSize * 2), + this.BindGroupEntry, + 40, + ), + }; + + const bindGroup = device.createBindGroup(descriptor); + return this.bindGroups.create(bindGroup); + }, + + /** + * @param {number} deviceIdx + * @param {number} descriptorPtr + * @returns {number} The bind group layout. + */ + wgpuDeviceCreateBindGroupLayout: (deviceIdx, descriptorPtr) => { + const device = this.devices.get(deviceIdx); + this.assert(descriptorPtr != 0); + + /** @type {GPUBindGroupLayoutDescriptor} */ + const descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + entries: this.array( + this.mem.loadUint(descriptorPtr + 8), + this.mem.loadPtr(descriptorPtr + 8 + this.mem.intSize), + this.BindGroupLayoutEntry, + 80, + ), + }; + + const bindGroupLayout = device.createBindGroupLayout(descriptor); + return this.bindGroupLayouts.create(bindGroupLayout); + }, + + /** + * @param {number} deviceIdx + * @param {number} descriptorPtr + * @returns {number} The buffer. + */ + wgpuDeviceCreateBuffer: (deviceIdx, descriptorPtr) => { + const device = this.devices.get(deviceIdx); + this.assert(descriptorPtr != 0); + + /** @type {GPUBufferDescriptor} */ + const descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + usage: this.mem.loadU32(descriptorPtr + 8), + size: this.mem.loadU64(descriptorPtr + 16), + mappedAtCreation: this.mem.loadB32(descriptorPtr + 24), + }; + + const buffer = device.createBuffer(descriptor); + return this.buffers.create({buffer: buffer, mapping: null}); + }, + + /** + * @param {number} deviceIdx + * @param {0|number} descriptorPtr + * @returns {number} The command encoder. + */ + wgpuDeviceCreateCommandEncoder: (deviceIdx, descriptorPtr) => { + const device = this.devices.get(deviceIdx); + + /** @type {GPUCommandEncoderDescriptor} */ + let descriptor; + if (descriptor != 0) { + descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + }; + } + + const commandEncoder = device.createCommandEncoder(descriptor); + return this.commandEncoders.create(commandEncoder); + }, + + /** + * @param {number} deviceIdx + * @param {number} descriptorPtr + * @returns {number} The compute pipeline. + */ + wgpuDeviceCreateComputePipeline: (deviceIdx, descriptorPtr) => { + const device = this.devices.get(deviceIdx); + this.assert(descriptorPtr != 0); + const computePipeline = device.createComputePipeline(this.ComputePipelineDescriptor(descriptorPtr)); + return this.computePipelines.create(computePipeline); + }, + + /** + * @param {number} deviceIdx + * @param {number} descriptorPtr + * @param {number} callbackPtr + * @param {number} userdata + */ + wgpuDeviceCreateComputePipelineAsync: async (deviceIdx, descriptorPtr, callbackPtr, userdata) => { + const device = this.devices.get(deviceIdx); + const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); + this.assert(descriptorPtr != 0); + + let result; + let resultIdx; + try { + const computePipeline = await device.createComputePipelineAsync(this.ComputePipelineDescriptor(descriptorPtr)); + resultIdx = this.computePipelines.create(computePipeline); + result = 0; /* Success */ + // NOTE: don't callback here, any errors that happen later will then be caught by the catch here. + } catch (e) { + console.warn(e); + result = 5; /* Unknown error */ + } + + callback(result, resultIdx, null, userdata); + }, + + /** + * @param {number} deviceIdx + * @param {number} descriptorPtr + * @returns {number} The pipeline layout. + */ + wgpuDeviceCreatePipelineLayout: (deviceIdx, descriptorPtr) => { + const device = this.devices.get(deviceIdx); + this.assert(descriptorPtr != 0); + + /** @type {GPUPipelineLayoutDescriptor} */ + const descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + bindGroupLayouts: this.array( + this.mem.loadUint(descriptorPtr + 8), + this.mem.loadPtr(descriptorPtr + 8 + this.mem.intSize), + (ptr) => this.bindGroupLayouts.get(this.mem.loadPtr(ptr)), + 4, + ), + }; + + const pipelineLayout = device.createPipelineLayout(descriptor); + return this.pipelineLayouts.create(pipelineLayout); + }, + + /** + * @param {number} deviceIdx + * @param {number} descriptorPtr + * @returns {number} The query set. + */ + wgpuDeviceCreateQuerySet: (deviceIdx, descriptorPtr) => { + const device = this.devices.get(deviceIdx); + this.assert(descriptorPtr != 0); + + /** @type {GPUQuerySetDescriptor} */ + const descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + type: this.QueryType(descriptorPtr + 8), + count: this.mem.loadU32(descriptorPtr + 12), + }; + + const querySet = device.createQuerySet(descriptor); + return this.querySets.create(querySet); + }, + + /** + * @param {number} deviceIdx + * @param {number} descriptorPtr + * @returns {number} The query set. + */ + wgpuDeviceCreateRenderBundleEncoder: (deviceIdx, descriptorPtr) => { + const device = this.devices.get(deviceIdx); + this.assert(descriptorPtr != 0); + + /** @type {GPURenderBundleEncoderDescriptor} */ + const descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + colorFormats: this.array( + this.mem.loadUint(descriptorPtr + 8), + this.mem.loadPtr(descriptorPtr + 8 + this.mem.intSize), + this.TextureFormat, + 4, + ), + depthStencilFormat: this.enumeration("TextureFormat", descriptorPtr + 8 + this.mem.intSize + 4), + sampleCount: this.mem.loadU32(descriptorPtr + 8 + this.mem.intSize + 8), + depthReadOnly: this.mem.loadB32(descriptorPtr + 8 + this.mem.intSize + 12), + stencilReadOnly: this.mem.loadB32(descriptorPtr + 8 + this.mem.intSize + 16), + }; + + const renderBundleEncoder = device.createRenderBundleEncoder(descriptor); + return this.renderBundleEncoders.create(renderBundleEncoder); + }, + + /** + * @param {number} deviceIdx + * @param {number} descriptorPtr + * @returns {number} The render pipeline. + */ + wgpuDeviceCreateRenderPipeline: (deviceIdx, descriptorPtr) => { + const device = this.devices.get(deviceIdx); + this.assert(descriptorPtr != 0); + + const descriptor = this.RenderPipelineDescriptor(descriptorPtr); + const renderPipeline = device.createRenderPipeline(descriptor); + return this.renderPipelines.create(renderPipeline); + }, + + /** + * @param {number} deviceIdx + * @param {number} descriptorPtr + * @param {number} callbackPtr + * @param {number} userdata + */ + wgpuDeviceCreateRenderPipelineAsync: async (deviceIdx, descriptorPtr, callbackPtr, userdata) => { + const device = this.devices.get(deviceIdx); + const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); + this.assert(descriptorPtr != 0); + + let result; + let resultIdx; + try { + const renderPipeline = await device.createRenderPipelineAsync(this.RenderPipelineDescriptor(descriptorPtr)); + resultIdx = this.renderPipelines.create(renderPipeline); + result = 0; /* Success */ + // NOTE: don't callback here, any errors that happen later will then be caught by the catch here. + } catch (e) { + console.warn(e); + result = 5; /* Unknown error */ + } + + callback(result, resultIdx, null, userdata); + }, + + /** + * @param {number} deviceIdx + * @param {0|number} descriptorPtr + * @returns {number} The sampler. + */ + wgpuDeviceCreateSampler: (deviceIdx, descriptorPtr) => { + const device = this.devices.get(deviceIdx); + + /** @type {?GPUSamplerDescriptor} */ + let descriptor; + if (descriptorPtr != 0) { + descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + addressModeU: this.enumeration("AddressMode", descriptorPtr + 8), + addressModeV: this.enumeration("AddressMode", descriptorPtr + 12), + addressModeW: this.enumeration("AddressMode", descriptorPtr + 16), + magFilter: this.enumeration("FilterMode", descriptorPtr + 20), + minFilter: this.enumeration("FilterMode", descriptorPtr + 24), + mipMapFilter: this.enumeration("MipmapFilterMode", descriptorPtr + 28), + lodMinClamp: this.mem.loadF32(descriptorPtr + 32), + lodMaxClamp: this.mem.loadF32(descriptorPtr + 36), + compare: this.enumeration("CompareFunction", descriptorPtr + 40), + maxAnisotropy: this.mem.loadU16(descriptorPtr + 44), + }; + } + + const sampler = device.createSampler(descriptor); + return this.samplers.create(sampler); + }, + + /** + * @param {number} deviceIdx + * @param {number} descriptorPtr + * @returns {number} The shader module. + */ + wgpuDeviceCreateShaderModule: (deviceIdx, descriptorPtr) => { + const device = this.devices.get(deviceIdx); + this.assert(descriptorPtr != 0); + + const nextInChain = this.mem.loadPtr(descriptorPtr); + const nextInChainType = this.mem.loadI32(nextInChain + 4); + + // ShaderModuleWGSLDescriptor = 0x00000006, + if (nextInChainType != 6) { + throw new TypeError(`Descriptor type should be 'ShaderModuleWGSLDescriptor', got ${nextInChainType}`); + } + + /** @type {GPUShaderModuleDescriptor} */ + const descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + code: this.mem.loadCstring(nextInChain + 8), + compilationHints: this.array( + this.mem.loadUint(descriptorPtr + 8), + this.mem.loadPtr(descriptorPtr + 8 + this.mem.intSize), + this.ShaderModuleCompilationHint, + 12, + ), + }; + + const shaderModule = device.createShaderModule(descriptor); + return this.shaderModules.create(shaderModule); + }, + + /** + * @param {number} deviceIdx + * @param {number} descriptorPtr + * @returns {number} The texture. + */ + wgpuDeviceCreateTexture: (deviceIdx, descriptorPtr) => { + const device = this.devices.get(deviceIdx); + this.assert(descriptorPtr != 0); + + /** @type {GPUTextureDescriptor} */ + const descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + usage: this.mem.loadU32(descriptorPtr + 8), + dimension: this.enumeration("TextureDimension", descriptorPtr + 12), + size: this.Extent3D(descriptorPtr + 16), + format: this.enumeration("TextureFormat", descriptorPtr + 28), + mipLevelCount: this.mem.loadU32(descriptorPtr + 32), + sampleCount: this.mem.loadU32(descriptorPtr + 36), + viewFormats: this.array( + this.mem.loadUint(descriptorPtr + 40), + this.mem.loadPtr(descriptorPtr + 40 + this.mem.intSize), + (ptr) => this.enumeration("TextureFormat", ptr), + 4, + ), + }; + + const texture = device.createTexture(descriptor); + return this.textures.create(texture); + }, + + /** + * @param {number} deviceIdx + */ + wgpuDeviceDestroy: (deviceIdx) => { + const device = this.devices.get(deviceIdx); + device.destroy(); + }, + + /** + * @param {number} deviceIdx + * @param {number} featuresPtr + * @returns {number|BigInt} + */ + wgpuDeviceEnumerateFeatures: (deviceIdx, featuresPtr) => { + const device = this.devices.get(deviceIdx); + return this.genericEnumerateFeatures(device.features, featuresPtr); + }, + + /** + * @param {number} deviceIdx + * @param {number} limitsPtr + * @returns {boolean} + */ + wgpuDeviceGetLimits: (deviceIdx, limitsPtr) => { + const device = this.devices.get(deviceIdx); + return this.genericGetLimits(device.limits, limitsPtr); + }, + + /** + * @param {number} deviceIdx + * @returns {number} + */ + wgpuDeviceGetQueue: (deviceIdx) => { + const device = this.devices.get(deviceIdx); + return this.queues.create(device.queue); + }, + + /** + * @param {number} deviceIdx + * @param {number} featureInt + * @returns {boolean} + */ + wgpuDeviceHasFeature: (deviceIdx, featureInt) => { + const device = this.devices.get(deviceIdx); + return device.features.has(this.enums.FeatureName[featureInt]); + }, + + /** + * @param {number} deviceIdx + * @param {number} callbackPtr + * @param {number} userdata + */ + wgpuDevicePopErrorScope: async (deviceIdx, callbackPtr, userdata) => { + const device = this.devices.get(deviceIdx); + const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); + const error = await device.popErrorScope(); + if (!error) { + callback(0, null, userdata); + return; + } + console.warn(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); + }, + + /** + * @param {number} deviceIdx + * @param {number} filterInt + */ + wgpuDevicePushErrorScope: (deviceIdx, filterInt) => { + const device = this.devices.get(deviceIdx); + 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 ---------------------- */ + + /** + * @param {number} instanceIdx + * @param {number} descriptorPtr + */ + wgpuInstanceCreateSurface: (instanceIdx, descriptorPtr) => { + this.assert(instanceIdx > 0); + this.assert(descriptorPtr != 0); + + const nextInChain = this.mem.loadPtr(descriptorPtr); + const nextInChainType = this.mem.loadI32(nextInChain + 4); + + // SurfaceDescriptorFromCanvasHTMLSelector = 0x00000004, + if (nextInChainType != 4) { + throw new TypeError(`Descriptor type should be 'SurfaceDescriptorFromCanvasHTMLSelector', got ${nextInChainType}`); + } + + const selector = this.mem.loadCstring(nextInChain + 8); + const surface = document.querySelector(selector); + if (!surface) { + throw new Error(`Selector '${selector}' did not match any element`); + } + if (!(surface instanceof HTMLCanvasElement)) { + throw new Error('Selector matches an element that is not a canvas'); + } + + return this.surfaces.create(surface); + }, + + /** + * @param {number} instanceIdx + * @param {number} featureInt + * @returns {boolean} + */ + wgpuInstanceHasWGSLLanguageFeature: (instanceIdx, featureInt) => { + return navigator.gpu.wgslLanguageFeatures.has(this.enums.WGSLFeatureName[featureInt]); + }, + + /** + * @param {number} instanceIdx + */ + wgpuInstanceProcessEvents: (instanceIdx) => { + console.warn("unimplemented: wgpuInstanceProcessEvents"); + }, + + /** + * @param {number} instanceIdx + * @param {0|number} optionsPtr + * @param {number} callbackPtr + * @param {number} userdata + */ + wgpuInstanceRequestAdapter: async (instanceIdx, optionsPtr, callbackPtr, userdata) => { + this.assert(instanceIdx > 0); + const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); + + /** @type {GPURequestAdapterOptions} */ + let options; + if (optionsPtr != 0) { + options = { + powerPreference: this.enumeration("PowerPreference", optionsPtr + 8), + forceFallbackAdapter: this.mem.loadB32(optionsPtr + 16), + }; + } + + let adapterIdx; + try { + const adapter = await navigator.gpu.requestAdapter(options); + adapterIdx = this.adapters.create(adapter); + // NOTE: don't callback here, any errors that happen later will then be caught by the catch here. + } catch(e) { + console.warn(e); + callback(2, null, null, userdata); + } + + callback(0, adapterIdx, null, userdata); + }, + + ...this.instances.interface(false), + + /* ---------------------- PipelineLayout ---------------------- */ + + ...this.pipelineLayouts.interface(true), + + /* ---------------------- QuerySet ---------------------- */ + + /** + * @param {number} querySetIdx + */ + wgpuQuerySetDestroy: (querySetIdx) => { + const querySet = this.querySets.get(querySetIdx); + querySet.destroy(); + }, + + /** + * @param {number} querySetIdx + * @returns {number} + */ + wgpuQuerySetGetCount: (querySetIdx) => { + const querySet = this.querySets.get(querySetIdx); + return querySet.count; + }, + + /** + * @param {number} querySetIdx + * @returns {number} + */ + wgpuQuerySetGetType: (querySetIdx) => { + const querySet = this.querySets.get(querySetIdx); + return this.enums.QueryType.indexOf(querySet.type); + }, + + ...this.querySets.interface(true), + + /* ---------------------- Queue ---------------------- */ + + /** + * @param {number} queueIdx + * @param {number} callbackPtr + * @param {number} userdata + */ + wgpuQueueOnSubmittedWorkDone: async (queueIdx, callbackPtr, userdata) => { + const queue = this.queues.get(queueIdx); + const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); + let result; + try { + await queue.onSubmittedWorkDone(); + result = 0; + } catch(e) { + console.warn(e); + result = 1; + } + callback(result, userdata); + }, + + /** + * @param {number} queueIdx + * @param {BigInt|number} commandCount + * @param {number} commandsPtr + */ + wgpuQueueSubmit: (queueIdx, commandCount, commandsPtr) => { + const queue = this.queues.get(queueIdx); + const commands = this.array( + this.unwrapBigInt(commandCount), + commandsPtr, + (ptr) => this.commandBuffers.get(this.mem.loadPtr(ptr)), + 4, + ); + queue.submit(commands); + }, + + /** + * @param {number} queueIdx + * @param {number} bufferIdx + * @param {BigInt} bufferOffset + * @param {number} dataPtr + * @param {number|BigInt} size + */ + wgpuQueueWriteBuffer: (queueIdx, bufferIdx, bufferOffset, dataPtr, size) => { + const queue = this.queues.get(queueIdx); + const buffer = this.buffers.get(bufferIdx); + bufferOffset = this.unwrapBigInt(bufferOffset); + size = this.unwrapBigInt(size); + queue.writeBuffer(buffer.buffer, bufferOffset, this.mem.loadBytes(dataPtr, size), 0, size); + }, + + /** + * @param {number} queueIdx + * @param {number} destinationPtr + * @param {number} dataPtr + * @param {number|BigInt} dataSize + * @param {number} dataLayoutPtr + * @param {number} writeSizePtr + */ + wgpuQueueWriteTexture: (queueIdx, destinationPtr, dataPtr, dataSize, dataLayoutPtr, writeSizePtr) => { + const queue = this.queues.get(queueIdx); + const destination = this.ImageCopyTexture(destinationPtr); + dataSize = this.unwrapBigInt(dataSize); + const dataLayout = this.TextureDataLayout(dataLayoutPtr); + const writeSize = this.Extent3D(writeSizePtr); + queue.writeTexture(destination, this.mem.loadBytes(dataPtr, dataSize), dataLayout, writeSize); + }, + + ...this.queues.interface(true), + + /* ---------------------- RenderBundle ---------------------- */ + + ...this.renderBundles.interface(true), + + /* ---------------------- RenderBundleEncoder ---------------------- */ + + /** + * @param {number} renderBundleEncoderIdx + * @param {number} vertexCount + * @param {number} instanceCount + * @param {number} firstVertex + * @param {number} firstInstance + */ + wgpuRenderBundleEncoderDraw: (renderBundleEncoderIdx, vertexCount, instanceCount, firstVertex, firstInstance) => { + const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); + renderBundleEncoder.draw(vertexCount, instanceCount, firstVertex, firstInstance); + }, + + /** + * @param {number} renderBundleEncoderIdx + * @param {number} indexCount + * @param {number} instanceCount + * @param {number} firstIndex + * @param {number} baseVertex + * @param {number} firstInstance + */ + wgpuRenderBundleEncoderDrawIndexed: (renderBundleEncoderIdx, indexCount, instanceCount, firstIndex, baseVertex, firstInstance) => { + const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); + renderBundleEncoder.drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance); + }, + + /** + * @param {number} renderBundleEncoderIdx + * @param {number} indirectBufferIdx + * @param {BigInt} indirectOffset + */ + wgpuRenderBundleEncoderDrawIndexedIndirect: (renderBundleEncoderIdx, indirectBufferIdx, indirectOffset) => { + const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); + indirectOffset = this.unwrapBigInt(indirectOffset); + const buffer = this.buffers.get(indirectBufferIdx); + renderBundleEncoder.drawIndexedIndirect(buffer.buffer, indirectOffset); + }, + + /** + * @param {number} renderBundleEncoderIdx + * @param {number} indirectBufferIdx + * @param {BigInt} indirectOffset + */ + wgpuRenderBundleEncoderDrawIndirect: (renderBundleEncoderIdx, indirectBufferIdx, indirectOffset) => { + const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); + indirectOffset = this.unwrapBigInt(indirectOffset); + const buffer = this.buffers.get(indirectBufferIdx); + renderBundleEncoder.drawIndirect(buffer.buffer, indirectOffset); + }, + + /** + * @param {number} renderBundleEncoderIdx + * @param {0|number} descriptorPtr + * @returns {number} + */ + wgpuRenderBundleEncoderFinish: (renderBundleEncoderIdx, descriptorPtr) => { + const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); + + /** @type {?GPURenderBundleDescriptor} */ + let descriptor; + if (descriptorPtr != 0) { + descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + }; + } + + const renderBundle = renderBundleEncoder.finish(descriptor); + return this.renderBundles.create(renderBundle); + }, + + /** + * @param {number} renderBundleEncoderIdx + * @param {number} markerLabelPtr + */ + wgpuRenderBundleEncoderInsertDebugMarker: (renderBundleEncoderIdx, markerLabelPtr) => { + const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); + this.assert(markerLabelPtr != 0); + const markerLabel = this.mem.loadCstring(markerLabelPtr); + renderBundleEncoder.insertDebugMarker(markerLabel); + }, + + /** + * @param {number} renderBundleEncoderIdx + */ + wgpuRenderBundleEncoderPopDebugGroup: (renderBundleEncoderIdx) => { + const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); + renderBundleEncoder.popDebugGroup(); + }, + + /** + * @param {number} renderBundleEncoderIdx + * @param {number} groupLabelPtr + */ + wgpuRenderBundleEncoderPushDebugGroup: (renderBundleEncoderIdx, groupLabelPtr) => { + const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); + this.assert(groupLabelPtr!= 0); + const groupLabel = this.mem.loadCstring(groupLabelPtr); + renderBundleEncoder.pushDebugGroup(groupLabel); + }, + + /** + * @param {number} renderBundleEncoderIdx + * @param {number} groupIndex + * @param {0|number} groupIdx + * @param {number|BigInt} dynamicOffsetCount + * @param {number} dynamicOffsetsPtr + */ + wgpuRenderBundleEncoderSetBindGroup: (renderBundleEncoderIdx, groupIndex, groupIdx, dynamicOffsetCount, dynamicOffsetsPtr) => { + const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); + + let group; + if (groupIdx > 0) { + group = this.bindGroups.get(groupIdx); + } + + dynamicOffsetCount = this.unwrapBigInt(dynamicOffsetCount); + const dynamicOffsets = this.array(dynamicOffsetCount, dynamicOffsetsPtr, this.mem.loadU32, 4); + + renderBundleEncoder.setBindGroup(groupIndex, group, dynamicOffsets); + }, + + /** + * @param {number} renderBundleEncoderIdx + * @param {number} bufferIdx + * @param {number} formatInt + * @param {BigInt} offset + * @param {BigInt} size + */ + wgpuRenderBundleEncoderSetIndexBuffer: (renderBundleEncoderIdx, bufferIdx, formatInt, offset, size) => { + const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); + const buffer = this.buffers.get(bufferIdx); + const format = this.enums.IndexFormat[formatInt]; + offset = this.unwrapBigInt(offset); + size = this.unwrapBigInt(size); + renderBundleEncoder.setIndexBuffer(buffer.buffer, format, offset, size); + }, + + /** + * @param {number} renderBundleEncoderIdx + * @param {number} pipelineIdx + */ + wgpuRenderBundleEncoderSetPipeline: (renderBundleEncoderIdx, pipelineIdx) => { + const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); + const pipeline = this.renderPipelines.get(pipelineIdx); + renderBundleEncoder.setPipeline(pipeline); + }, + + /** + * @param {number} renderBundleEncoderIdx + * @param {number} slot + * @param {0|number} bufferIdx + * @param {BigInt} offset + * @param {BigInt} size + */ + wgpuRenderBundleEncoderSetVertexBuffer: (renderBundleEncoderIdx, slot, bufferIdx, offset, size) => { + const renderBundleEncoder = this.renderBundleEncoders.get(renderBundleEncoderIdx); + + let buffer; + if (bufferIdx > 0) { + buffer = this.buffers.get(bufferIdx).buffer; + } + + offset = this.unwrapBigInt(offset); + size = this.unwrapBigInt(size); + renderBundleEncoder.setVertexBuffer(slot, buffer, offset, size); + }, + + ...this.renderBundleEncoders.interface(true), + + /* ---------------------- RenderPassEncoder ---------------------- */ + + /** + * @param {number} renderPassEncoderIdx + * @param {number} queryIndex + */ + wgpuRenderPassEncoderBeginOcclusionQuery: (renderPassEncoderIdx, queryIndex) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + renderPassEncoder.beginOcclusionQuery(queryIndex); + }, + + /** + * @param {number} renderPassEncoderIdx + * @param {number} vertexCount + * @param {number} instanceCount + * @param {number} firstVertex + * @param {number} firstInstance + */ + wgpuRenderPassEncoderDraw: (renderPassEncoderIdx, vertexCount, instanceCount, firstVertex, firstInstance) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + renderPassEncoder.draw(vertexCount, instanceCount, firstVertex, firstInstance); + }, + + /** + * @param {number} renderPassEncoderIdx + * @param {number} indexCount + * @param {number} instanceCount + * @param {number} firstIndex + * @param {number} baseVertex + * @param {number} firstInstance + */ + wgpuRenderPassEncoderDrawIndexed: (renderPassEncoderIdx, indexCount, instanceCount, firstIndex, baseVertex, firstInstance) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + renderPassEncoder.drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance); + }, + + /** + * @param {number} renderPassEncoderIdx + * @param {number} indirectBufferIdx + * @param {BigInt} indirectOffset + */ + wgpuRenderPassEncoderDrawIndexedIndirect: (renderPassEncoderIdx, indirectBufferIdx, indirectOffset) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + const buffer = this.buffers.get(indirectBufferIdx); + indirectOffset = this.unwrapBigInt(indirectOffset); + renderPassEncoder.drawIndexedIndirect(buffer.buffer, indirectOffset); + }, + + /** + * @param {number} renderPassEncoderIdx + * @param {number} indirectBufferIdx + * @param {BigInt} indirectOffset + */ + wgpuRenderPassEncoderDrawIndirect: (renderPassEncoderIdx, indirectBufferIdx, indirectOffset) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + const buffer = this.buffers.get(indirectBufferIdx); + indirectOffset = this.unwrapBigInt(indirectOffset); + renderPassEncoder.drawIndirect(buffer.buffer, indirectOffset); + }, + + /** + * @param {number} renderPassEncoderIdx + */ + wgpuRenderPassEncoderEnd: (renderPassEncoderIdx) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + renderPassEncoder.end(); + }, + + /** + * @param {number} renderPassEncoderIdx + */ + wgpuRenderPassEncoderEndOcclusionQuery: (renderPassEncoderIdx) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + renderPassEncoder.endOcclusionQuery(); + }, + + /** + * @param {number} renderPassEncoderIdx + * @param {number|BigInt} bundleCount + * @param {number} bundlesPtr + */ + wgpuRenderPassEncoderExecuteBundles: (renderPassEncoderIdx, bundleCount, bundlesPtr) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + bundleCount = this.unwrapBigInt(bundleCount); + const bundles = this.array( + bundleCount, + bundlesPtr, + (ptr) => this.renderBundles.get(this.mem.loadPtr(ptr)), + 4, + ); + renderPassEncoder.executeBundles(bundles); + }, + + /** + * @param {number} renderPassEncoderIdx + * @param {number} markerLabelPtr + */ + wgpuRenderPassEncoderInsertDebugMarker: (renderPassEncoderIdx, markerLabelPtr) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + const markerLabel = this.mem.loadCstring(markerLabelPtr); + renderPassEncoder.insertDebugMarker(markerLabel); + }, + + /** + * @param {number} renderPassEncoderIdx + */ + wgpuRenderPassEncoderPopDebugGroup: (renderPassEncoderIdx) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + renderPassEncoder.popDebugGroup(); + }, + + /** + * @param {number} renderPassEncoderIdx + * @param {number} groupLabelPtr + */ + wgpuRenderPassEncoderPushDebugGroup: (renderPassEncoderIdx, groupLabelPtr) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + const groupLabel = this.mem.loadCstring(groupLabelPtr); + renderPassEncoder.pushDebugGroup(groupLabel); + }, + + /** + * @param {number} renderPassEncoderIdx + * @param {number} groupIndex + * @param {0|number} groupIdx + * @param {number|BigInt} dynamicOffsetCount + * @param {number} dynamicOffsetsPtr + */ + wgpuRenderPassEncoderSetBindGroup: (renderPassEncoderIdx, groupIndex, groupIdx, dynamicOffsetCount, dynamicOffsetsPtr) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + + let group; + if (groupIdx > 0) { + group = this.bindGroups.get(groupIdx); + } + + dynamicOffsetCount = this.unwrapBigInt(dynamicOffsetCount); + const dynamicOffsets = this.array(dynamicOffsetCount, dynamicOffsetsPtr, this.mem.loadU32, 4); + + renderPassEncoder.setBindGroup(groupIndex, group, dynamicOffsets); + }, + + /** + * @param {number} renderPassEncoderIdx + * @param {number} bufferIdx + * @param {number} formatInt + * @param {BigInt} offset + * @param {BigInt} size + */ + wgpuRenderPassEncoderSetIndexBuffer: (renderPassEncoderIdx, bufferIdx, formatInt, offset, size) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + const buffer = this.buffers.get(bufferIdx); + const format = this.enums.IndexFormat[formatInt]; + offset = this.unwrapBigInt(offset); + size = this.unwrapBigInt(size); + renderPassEncoder.setIndexBuffer(buffer.buffer, format, offset, size); + }, + + /** + * @param {number} renderPassEncoderIdx + * @param {number} pipelineIdx + */ + wgpuRenderPassEncoderSetPipeline: (renderPassEncoderIdx, pipelineIdx) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + const pipeline = this.renderPipelines.get(pipelineIdx); + renderPassEncoder.setPipeline(pipeline); + }, + + /** + * @param {number} renderPassEncoderIdx + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + */ + wgpuRenderPassEncoderSetScissorRect: (renderPassEncoderIdx, x, y, width, height) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + renderPassEncoder.setScissorRect(x, y, width, height); + }, + + /** + * @param {number} renderPassEncoderIdx + * @param {number} reference + */ + wgpuRenderPassEncoderSetStencilReference: (renderPassEncoderIdx, reference) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + renderPassEncoder.setStencilReference(reference); + }, + + /** + * @param {number} renderPassEncoderIdx + * @param {number} slot + * @param {0|number} bufferIdx + * @param {BigInt} offset + * @param {BigInt} size + */ + wgpuRenderPassEncoderSetVertexBuffer: (renderPassEncoderIdx, slot, bufferIdx, offset, size) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + + let buffer; + if (bufferIdx > 0) { + buffer = this.buffers.get(bufferIdx).buffer; + } + + offset = this.unwrapBigInt(offset); + size = this.unwrapBigInt(size); + renderPassEncoder.setVertexBuffer(slot, buffer, offset, size); + }, + + /** + * @param {number} renderPassEncoderIdx + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {number} minDepth + * @param {number} maxDepth + */ + wgpuRenderPassEncoderSetViewport: (renderPassEncoderIdx, x, y, width, height, minDepth, maxDepth) => { + const renderPassEncoder = this.renderPassEncoders.get(renderPassEncoderIdx); + renderPassEncoder.setViewport(x, y, width, height, minDepth, maxDepth); + }, + + ...this.renderPassEncoders.interface(true), + + /* ---------------------- RenderPipeline ---------------------- */ + + /** + * @param {number} renderPipelineIdx + * @param {number} groupIndex + * @returns {number} + */ + wgpuRenderPipelineGetBindGroupLayout: (renderPipelineIdx, groupIndex) => { + const renderPipeline = this.renderPipelines.get(renderPipelineIdx); + const bindGroupLayout = renderPipeline.getBindGroupLayout(groupIndex); + return this.bindGroupLayouts.create(bindGroupLayout); + }, + + ...this.renderPipelines.interface(true), + + /* ---------------------- Sampler ---------------------- */ + + ...this.samplers.interface(true), + + /* ---------------------- ShaderModule ---------------------- */ + + /** + * @param {number} shaderModuleIdx + * @param {number} callbackPtr + * @param {number} userdata + */ + wgpuShaderModuleGetCompilationInfo: async (shaderModuleIdx, callbackPtr, userdata) => { + const shaderModule = this.shaderModules.get(shaderModuleIdx); + const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); + + let status = 0; + let retAddr = 0; + + const ptrsToFree = []; + + try { + const compilationInfo = await shaderModule.getCompilationInfo(); + + const size = compilationInfo.messages.length * 72; + const addr = this.mem.exports.wgpu_alloc(size); + ptrsToFree.push(addr); + compilationInfo.messages.forEach((message, i) => { + const messageLength = new TextEncoder().encode(message.message).length; + const messageAddr = this.mem.exports.wgpu_alloc(messageLength); + ptrsToFree.push(messageAddr); + this.mem.storeString(messageAddr, message.message); + this.mem.storeI32(addr + (i * size) + 4); + + this.mem.storeI32(addr + (i * size) + 8, this.enums.CompilationMessageType.indexOf(message.type)); + + this.mem.storeU64(addr + (i * size) + 16, message.lineNum); + this.mem.storeU64(addr + (i * size) + 24, message.linePos); + this.mem.storeU64(addr + (i * size) + 32, message.offset); + this.mem.storeU64(addr + (i * size) + 40, message.length); + + // TODO: UTF16 units. + this.mem.storeU64(addr + (i * size) + 48, message.linePos); + this.mem.storeU64(addr + (i * size) + 56, message.offset); + this.mem.storeU64(addr + (i * size) + 64, message.length); + }); + + retAddr = this.mem.exports.wgpu_alloc(3*this.mem.intSize); + ptrsToFree.push(retAddr); + this.mem.storeUint(retAddr + this.mem.intSize, compilationInfo.messages.length); + this.mem.storeI32(retAddr + this.mem.intSize*2, addr); + } catch (e) { + console.warn(e); + status = 1; + } + + callback(status, retAddr, userdata); + + ptrsToFree.forEach(ptr => this.mem.exports.wgpu_free(ptr)); + }, + + ...this.shaderModules.interface(true), + + /* ---------------------- Surface ---------------------- */ + + /** + * @param {number} surfaceIdx + * @param {number} configPtr + */ + wgpuSurfaceConfigure: (surfaceIdx, configPtr) => { + const surface = this.surfaces.get(surfaceIdx); + const context = surface.getContext('webgpu'); + + const widthOff = 16 + this.mem.intSize + 8; + surface.width = this.mem.loadU32(configPtr + widthOff); + surface.height = this.mem.loadU32(configPtr + widthOff + 4); + + /** @type {GPUCanvasConfiguration} */ + const config = { + device: this.devices.get(this.mem.loadPtr(configPtr + 4)), + format: this.enumeration("TextureFormat", configPtr + 8), + usage: this.mem.loadU32(configPtr + 12), + viewFormats: this.array( + this.mem.loadUint(configPtr + 16), + this.mem.loadPtr(configPtr + 16 + this.mem.intSize), + (ptr) => this.enumeration("TextureFormat", ptr), + 4, + ), + alphaMode: this.enumeration("CompositeAlphaMode", configPtr + widthOff - 4), + // // NOTE: present mode seems unused. + presentMode: this.enumeration("PresentMode", configPtr + widthOff + 4), + }; + + context.configure(config); + }, + + /** + * @param {number} surfaceIdx + * @param {number} adapterIdx + * @param {number} capabilitiesPtr + */ + wgpuSurfaceGetCapabilities: (surfaceIdx, adapterIdx, capabilitiesPtr) => { + const formatStr = navigator.gpu.getPreferredCanvasFormat(); + const format = this.enums.TextureFormat.indexOf(formatStr); + + this.mem.storeUint(capabilitiesPtr + this.mem.intSize, 1); + const formatAddr = this.mem.exports.wgpu_alloc(4); + this.mem.storeI32(formatAddr, format); + this.mem.storeI32(capabilitiesPtr + this.mem.intSize*2, 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); + const presentModesAddr = this.mem.exports.wgpu_alloc(4); + this.mem.storeI32(presentModesAddr, 0); + this.mem.storeI32(capabilitiesPtr + this.mem.intSize*4, presentModesAddr); + + // Browser seems to support opaque (1) and premultiplied (2). + this.mem.storeUint(capabilitiesPtr + this.mem.intSize*5, 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); + }, + + /** + * @param {number} surfaceIdx + * @param {number} texturePtr + */ + wgpuSurfaceGetCurrentTexture: (surfaceIdx, texturePtr) => { + const surface = this.surfaces.get(surfaceIdx); + const context = surface.getContext('webgpu'); + const texture = context.getCurrentTexture(); + + const textureIdx = this.textures.create(texture); + this.mem.storeI32(texturePtr, textureIdx); + + // 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 + */ + wgpuSurfacePresent: (surfaceIdx) => { + // NOTE: Not really anything to do here. + }, + + /** + * @param {number} surfaceIdx + */ + wgpuSurfaceUnconfigure: (surfaceIdx) => { + const surface = this.surfaces.get(surfaceIdx); + surface.getContext('webgpu').unconfigure(); + }, + + ...this.surfaces.interface(true), + + /* ---------------------- SurfaceCapabilities ---------------------- */ + + /** + * @param {number} surfaceCapabilitiesPtr + */ + wgpuSurfaceCapabilitiesFreeMembers: (surfaceCapabilitiesPtr) => { + const formatsAddr = this.mem.loadI32(surfaceCapabilitiesPtr + this.mem.intSize*2); + this.mem.exports.wgpu_free(formatsAddr); + + const presentModesAddr = this.mem.loadI32(surfaceCapabilitiesPtr + this.mem.intSize*4); + this.mem.exports.wgpu_free(presentModesAddr); + + const alphaModesAddr = this.mem.loadI32(surfaceCapabilitiesPtr + this.mem.intSize*6); + this.mem.exports.wgpu_free(alphaModesAddr); + }, + + /* ---------------------- Texture ---------------------- */ + + /** + * @param {number} textureIdx + * @param {0|number} descriptorPtr + * @returns {number} + */ + wgpuTextureCreateView: (textureIdx, descriptorPtr) => { + const texture = this.textures.get(textureIdx); + + /** @type {?GPUTextureViewDescriptor} */ + let descriptor; + if (descriptorPtr != 0) { + descriptor = { + label: this.mem.loadCstring(descriptorPtr + 4), + format: this.enumeration("TextureFormat", descriptorPtr + 8), + dimension: this.enumeration("TextureViewDimension", descriptorPtr + 12), + baseMipLevel: this.mem.loadU32(descriptorPtr + 16), + mipLevelCount: this.mem.loadU32(descriptorPtr + 20), + baseArrayLayer: this.mem.loadU32(descriptorPtr + 24), + arrayLayerCount: this.mem.loadU32(descriptorPtr + 28), + aspect: this.enumeration("TextureAspect", descriptorPtr + 32), + }; + if (descriptor.arrayLayerCount == 0xFFFFFFFF) { + descriptor.arrayLayerCount = undefined; + } + if (descriptor.mipLevelCount == 0xFFFFFFFF) { + descriptor.mipLevelCount = undefined; + } + } + + const textureView = texture.createView(descriptor); + return this.textureViews.create(textureView); + }, + + /** + * @param {number} textureIdx + */ + wgpuTextureDestroy: (textureIdx) => { + const texture = this.textures.get(textureIdx); + texture.destroy(); + }, + + /** + * @param {number} textureIdx + * @returns {number} + */ + wgpuTextureDepthOrArrayLayers: (textureIdx) => { + const texture = this.textures.get(textureIdx); + return texture.depthOrArrayLayers; + }, + + /** + * @param {number} textureIdx + * @returns {number} + */ + wgpuTextureGetDimension: (textureIdx) => { + const texture = this.textures.get(textureIdx); + return this.enums.TextureDimension.indexOf(texture.dimension); + }, + + /** + * @param {number} textureIdx + * @returns {number} + */ + wgpuTextureGetFormat: (textureIdx) => { + const texture = this.textures.get(textureIdx); + return this.enums.TextureFormat.indexOf(texture.format); + }, + + /** + * @param {number} textureIdx + * @returns {number} + */ + wgpuTextureGetHeight: (textureIdx) => { + const texture = this.textures.get(textureIdx); + return texture.height; + }, + + /** + * @param {number} textureIdx + * @returns {number} + */ + wgpuTextureGetMipLevelCount: (textureIdx) => { + const texture = this.textures.get(textureIdx); + return texture.mipLevelCount; + }, + + /** + * @param {number} textureIdx + * @returns {number} + */ + wgpuTextureGetSampleCount: (textureIdx) => { + const texture = this.textures.get(textureIdx); + return texture.sampleCount; + }, + + /** + * @param {number} textureIdx + * @returns {number} + */ + wgpuTextureGetUsage: (textureIdx) => { + const texture = this.textures.get(textureIdx); + return texture.usage; + }, + + /** + * @param {number} textureIdx + * @returns {number} + */ + wgpuTextureGetWidth: (textureIdx) => { + const texture = this.textures.get(textureIdx); + return texture.width; + }, + + ...this.textures.interface(true), + + /* ---------------------- TextureView ---------------------- */ + + ...this.textureViews.interface(true), + }; + } +} + +/** @template T */ +class WebGPUObjectManager { + + /** + * @param {string} name + * @param {WasmMemoryInterface} mem + */ + constructor(name, mem) { + this.name = name; + this.mem = mem; + + this.idx = 0; + + /** @type {Record} */ + this.objects = {}; + } + + /** + * @param {T} object + * @returns {number} + */ + create(object) { + this.objects[this.idx] = { references: 1, object }; + this.idx += 1; + return this.idx; + } + + /** + * @param {number} idx + * @returns {T} + */ + get(idx) { + return this.objects[idx-1].object; + } + + /** @param {number} idx */ + release(idx) { + this.objects[idx-1].references -= 1; + if (this.objects[idx-1].references == 0) { + delete this.objects[idx-1]; + } + } + + /** @param {number} idx */ + reference(idx) { + this.objects[idx-1].references += 1; + } + + interface(withLabelSetter = false) { + const inter = {}; + inter[`wgpu${this.name}Reference`] = this.reference.bind(this); + inter[`wgpu${this.name}Release`] = this.release.bind(this); + if (withLabelSetter) { + inter[`wgpu${this.name}SetLabel`] = (idx, labelPtr) => { + const obj = this.get(idx); + obj.label = this.mem.loadCstring(labelPtr); + }; + } + return inter; + } +} + +window.odin = window.odin || {}; +window.odin.WebGPUInterface = WebGPUInterface; + +})(); diff --git a/vendor/wgpu/wgpu.odin b/vendor/wgpu/wgpu.odin new file mode 100644 index 000000000..4efe572cf --- /dev/null +++ b/vendor/wgpu/wgpu.odin @@ -0,0 +1,1708 @@ +package wgpu + +import "base:intrinsics" + +WGPU_SHARED :: #config(WGPU_SHARED, false) +WGPU_DEBUG :: #config(WGPU_DEBUG, false) + +@(private) TYPE :: "debug" when WGPU_DEBUG else "release" + +when ODIN_OS == .Windows { + @(private) ARCH :: "x86_64" when ODIN_ARCH == .amd64 else "x86_64" when ODIN_ARCH == .i386 else #panic("unsupported WGPU Native architecture") + @(private) EXT :: ".dll.lib" when WGPU_SHARED else ".lib" + @(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'") + } + + foreign import libwgpu { + LIB, + "system:d3dcompiler.lib", + "system:ws2_32.lib", + "system:userenv.lib", + "system:bcrypt.lib", + "system:ntdll.lib", + "system:opengl32.lib", + "system:advapi32.lib", + "system:user32.lib", + "system:gdi32.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") + @(private) EXT :: ".dylib" when WGPU_SHARED else ".a" + @(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'") + } + + foreign import libwgpu { + LIB, + "system:CoreFoundation.framework", + "system:QuartzCore.framework", + "system:Metal.framework", + } +} else when ODIN_OS == .Linux { + @(private) ARCH :: "x86_64" when ODIN_ARCH == .amd64 else "aarch64" when ODIN_ARCH == .arm64 else #panic("unsupported WGPU Native architecture") + @(private) EXT :: ".so" when WGPU_SHARED else ".a" + @(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'") + } + + foreign import libwgpu { + LIB, + "system:dl", + "system:m", + } +} else when ODIN_OS == .JS { + foreign import libwgpu "wgpu" +} + +ARRAY_LAYER_COUNT_UNDEFINED :: max(u32) +COPY_STRIDE_UNDEFINED :: max(u32) +DEPTH_SLICE_UNDEFINED :: max(u32) +LIMIT_U32_UNDEFINED :: max(u32) +LIMIT_U64_UNDEFINED :: max(u64) +MIP_LEVEL_COUNT_UNDEFINED :: max(u32) +QUERY_SET_INDEX_UNDEFINED :: max(u32) +WHOLE_MAP_SIZE :: max(uint) +WHOLE_SIZE :: max(u64) + +Flags :: u32 + +Adapter :: distinct rawptr +BindGroup :: distinct rawptr +BindGroupLayout :: distinct rawptr +Buffer :: distinct rawptr +CommandBuffer :: distinct rawptr +CommandEncoder :: distinct rawptr +ComputePassEncoder :: distinct rawptr +ComputePipeline :: distinct rawptr +Device :: distinct rawptr +Instance :: distinct rawptr +PipelineLayout :: distinct rawptr +QuerySet :: distinct rawptr +Queue :: distinct rawptr +RenderBundle :: distinct rawptr +RenderBundleEncoder :: distinct rawptr +RenderPassEncoder :: distinct rawptr +RenderPipeline :: distinct rawptr +Sampler :: distinct rawptr +ShaderModule :: distinct rawptr +Surface :: distinct rawptr +Texture :: distinct rawptr +TextureView :: distinct rawptr + +AdapterType :: enum i32 { + DiscreteGPU = 0x00000000, + IntegratedGPU = 0x00000001, + CPU = 0x00000002, + Unknown = 0x00000003, +} + +AddressMode :: enum i32 { + Repeat = 0x00000000, + MirrorRepeat = 0x00000001, + ClampToEdge = 0x00000002, +} + +BackendType :: enum i32 { + Undefined = 0x00000000, + Null = 0x00000001, + WebGPU = 0x00000002, + D3D11 = 0x00000003, + D3D12 = 0x00000004, + Metal = 0x00000005, + Vulkan = 0x00000006, + OpenGL = 0x00000007, + OpenGLES = 0x00000008, +} + +BlendFactor :: enum i32 { + Zero = 0x00000000, + One = 0x00000001, + Src = 0x00000002, + OneMinusSrc = 0x00000003, + SrcAlpha = 0x00000004, + OneMinusSrcAlpha = 0x00000005, + Dst = 0x00000006, + OneMinusDst = 0x00000007, + DstAlpha = 0x00000008, + OneMinusDstAlpha = 0x00000009, + SrcAlphaSaturated = 0x0000000A, + Constant = 0x0000000B, + OneMinusConstant = 0x0000000C, +} + +BlendOperation :: enum i32 { + Add = 0x00000000, + Subtract = 0x00000001, + ReverseSubtract = 0x00000002, + Min = 0x00000003, + Max = 0x00000004, +} + +BufferBindingType :: enum i32 { + Undefined = 0x00000000, + Uniform = 0x00000001, + Storage = 0x00000002, + ReadOnlyStorage = 0x00000003, +} + +BufferMapAsyncStatus :: enum i32 { + Success = 0x00000000, + ValidationError = 0x00000001, + Unknown = 0x00000002, + DeviceLost = 0x00000003, + DestroyedBeforeCallback = 0x00000004, + UnmappedBeforeCallback = 0x00000005, + MappingAlreadyPending = 0x00000006, + OffsetOutOfRange = 0x00000007, + SizeOutOfRange = 0x00000008, +} + +BufferMapState :: enum i32 { + Unmapped = 0x00000000, + Pending = 0x00000001, + Mapped = 0x00000002, +} + +CompareFunction :: enum i32 { + Undefined = 0x00000000, + Never = 0x00000001, + Less = 0x00000002, + LessEqual = 0x00000003, + Greater = 0x00000004, + GreaterEqual = 0x00000005, + Equal = 0x00000006, + NotEqual = 0x00000007, + Always = 0x00000008, +} + +CompilationInfoRequestStatus :: enum i32 { + Success = 0x00000000, + Error = 0x00000001, + DeviceLost = 0x00000002, + Unknown = 0x00000003, +} + +CompilationMessageType :: enum i32 { + Error = 0x00000000, + Warning = 0x00000001, + Info = 0x00000002, +} + +CompositeAlphaMode :: enum i32 { + Auto = 0x00000000, + Opaque = 0x00000001, + Premultiplied = 0x00000002, + Unpremultiplied = 0x00000003, + Inherit = 0x00000004, +} + +CreatePipelineAsyncStatus :: enum i32 { + Success = 0x00000000, + ValidationError = 0x00000001, + InternalError = 0x00000002, + DeviceLost = 0x00000003, + DeviceDestroyed = 0x00000004, + Unknown = 0x00000005, +} + +CullMode :: enum i32 { + None = 0x00000000, + Front = 0x00000001, + Back = 0x00000002, +} + +DeviceLostReason :: enum i32 { + Undefined = 0x00000000, + Destroyed = 0x00000001, +} + +ErrorFilter :: enum i32 { + Validation = 0x00000000, + OutOfMemory = 0x00000001, + Internal = 0x00000002, +} + +ErrorType :: enum i32 { + NoError = 0x00000000, + Validation = 0x00000001, + OutOfMemory = 0x00000002, + Internal = 0x00000003, + Unknown = 0x00000004, + DeviceLost = 0x00000005, +} + +FeatureName :: enum i32 { + // WebGPU. + Undefined = 0x00000000, + DepthClipControl = 0x00000001, + Depth32FloatStencil8 = 0x00000002, + TimestampQuery = 0x00000003, + TextureCompressionBC = 0x00000004, + TextureCompressionETC2 = 0x00000005, + TextureCompressionASTC = 0x00000006, + IndirectFirstInstance = 0x00000007, + ShaderF16 = 0x00000008, + RG11B10UfloatRenderable = 0x00000009, + BGRA8UnormStorage = 0x0000000A, + Float32Filterable = 0x0000000B, + + // Native. + PushConstants = 0x00030001, + TextureAdapterSpecificFormatFeatures, + MultiDrawIndirect, + MultiDrawIndirectCount, + VertexWritableStorage, + TextureBindingArray, + SampledTextureAndStorageBufferArrayNonUniformIndexing, + PipelineStatisticsQuery, + StorageResourceBindingArray, + PartiallyBoundBindingArray, +} + +FilterMode :: enum i32 { + Nearest = 0x00000000, + Linear = 0x00000001, +} + +FrontFace :: enum i32 { + CCW = 0x00000000, + CW = 0x00000001, +} + +IndexFormat :: enum i32 { + Undefined = 0x00000000, + Uint16 = 0x00000001, + Uint32 = 0x00000002, +} + +LoadOp :: enum i32 { + Undefined = 0x00000000, + Clear = 0x00000001, + Load = 0x00000002, +} + +MipmapFilterMode :: enum i32 { + Nearest = 0x00000000, + Linear = 0x00000001, +} + +PowerPreference :: enum i32 { + Undefined = 0x00000000, + LowPower = 0x00000001, + HighPerformance = 0x00000002, +} + +PresentMode :: enum i32 { + Fifo = 0x00000000, + FifoRelaxed = 0x00000001, + Immediate = 0x00000002, + Mailbox = 0x00000003, +} + +PrimitiveTopology :: enum i32 { + PointList = 0x00000000, + LineList = 0x00000001, + LineStrip = 0x00000002, + TriangleList = 0x00000003, + TriangleStrip = 0x00000004, +} + +QueryType :: enum i32 { + // WebGPU. + Occlusion = 0x00000000, + Timestamp = 0x00000001, + + // Native. + PipelineStatistics = 0x00030000, +} + +QueueWorkDoneStatus :: enum i32 { + Success = 0x00000000, + Error = 0x00000001, + Unknown = 0x00000002, + DeviceLost = 0x00000003, +} + +RequestAdapterStatus :: enum i32 { + Success = 0x00000000, + Unavailable = 0x00000001, + Error = 0x00000002, + Unknown = 0x00000003, +} + +RequestDeviceStatus :: enum i32 { + Success = 0x00000000, + Error = 0x00000001, + Unknown = 0x00000002, +} + +SType :: enum i32 { + // WebGPU. + Invalid = 0x00000000, + SurfaceDescriptorFromMetalLayer = 0x00000001, + SurfaceDescriptorFromWindowsHWND = 0x00000002, + SurfaceDescriptorFromXlibWindow = 0x00000003, + SurfaceDescriptorFromCanvasHTMLSelector = 0x00000004, + ShaderModuleSPIRVDescriptor = 0x00000005, + ShaderModuleWGSLDescriptor = 0x00000006, + PrimitiveDepthClipControl = 0x00000007, + SurfaceDescriptorFromWaylandSurface = 0x00000008, + SurfaceDescriptorFromAndroidNativeWindow = 0x00000009, + SurfaceDescriptorFromXcbWindow = 0x0000000A, + RenderPassDescriptorMaxDrawCount = 0x0000000F, + + // Native. + DeviceExtras = 0x00030001, + RequiredLimitsExtras, + PipelineLayoutExtras, + ShaderModuleGLSLDescriptor, + SupportedLimitsExtras, + InstanceExtras, + BindGroupEntryExtras, + BindGroupLayoutEntryExtras, + QuerySetDescriptorExtras, + SurfaceConfigurationExtras, +} + +SamplerBindingType :: enum i32 { + Undefined = 0x00000000, + Filtering = 0x00000001, + NonFiltering = 0x00000002, + Comparison = 0x00000003, +} + +StencilOperation :: enum i32 { + Keep = 0x00000000, + Zero = 0x00000001, + Replace = 0x00000002, + Invert = 0x00000003, + IncrementClamp = 0x00000004, + DecrementClamp = 0x00000005, + IncrementWrap = 0x00000006, + DecrementWrap = 0x00000007, +} + +StorageTextureAccess :: enum i32 { + Undefined = 0x00000000, + WriteOnly = 0x00000001, + ReadOnly = 0x00000002, + ReadWrite = 0x00000003, +} + +StoreOp :: enum i32 { + Undefined = 0x00000000, + Store = 0x00000001, + Discard = 0x00000002, +} + +SurfaceGetCurrentTextureStatus :: enum i32 { + Success = 0x00000000, + Timeout = 0x00000001, + Outdated = 0x00000002, + Lost = 0x00000003, + OutOfMemory = 0x00000004, + DeviceLost = 0x00000005, +} + +TextureAspect :: enum i32 { + All = 0x00000000, + StencilOnly = 0x00000001, + DepthOnly = 0x00000002, +} + +TextureDimension :: enum i32 { + _1D = 0x00000000, + _2D = 0x00000001, + _3D = 0x00000002, +} + +TextureFormat :: enum i32 { + Undefined = 0x00000000, + R8Unorm = 0x00000001, + R8Snorm = 0x00000002, + R8Uint = 0x00000003, + R8Sint = 0x00000004, + R16Uint = 0x00000005, + R16Sint = 0x00000006, + R16Float = 0x00000007, + RG8Unorm = 0x00000008, + RG8Snorm = 0x00000009, + RG8Uint = 0x0000000A, + RG8Sint = 0x0000000B, + R32Float = 0x0000000C, + R32Uint = 0x0000000D, + R32Sint = 0x0000000E, + RG16Uint = 0x0000000F, + RG16Sint = 0x00000010, + RG16Float = 0x00000011, + RGBA8Unorm = 0x00000012, + RGBA8UnormSrgb = 0x00000013, + RGBA8Snorm = 0x00000014, + RGBA8Uint = 0x00000015, + RGBA8Sint = 0x00000016, + BGRA8Unorm = 0x00000017, + BGRA8UnormSrgb = 0x00000018, + RGB10A2Uint = 0x00000019, + RGB10A2Unorm = 0x0000001A, + RG11B10Ufloat = 0x0000001B, + RGB9E5Ufloat = 0x0000001C, + RG32Float = 0x0000001D, + RG32Uint = 0x0000001E, + RG32Sint = 0x0000001F, + RGBA16Uint = 0x00000020, + RGBA16Sint = 0x00000021, + RGBA16Float = 0x00000022, + RGBA32Float = 0x00000023, + RGBA32Uint = 0x00000024, + RGBA32Sint = 0x00000025, + Stencil8 = 0x00000026, + Depth16Unorm = 0x00000027, + Depth24Plus = 0x00000028, + Depth24PlusStencil8 = 0x00000029, + Depth32Float = 0x0000002A, + Depth32FloatStencil8 = 0x0000002B, + BC1RGBAUnorm = 0x0000002C, + BC1RGBAUnormSrgb = 0x0000002D, + BC2RGBAUnorm = 0x0000002E, + BC2RGBAUnormSrgb = 0x0000002F, + BC3RGBAUnorm = 0x00000030, + BC3RGBAUnormSrgb = 0x00000031, + BC4RUnorm = 0x00000032, + BC4RSnorm = 0x00000033, + BC5RGUnorm = 0x00000034, + BC5RGSnorm = 0x00000035, + BC6HRGBUfloat = 0x00000036, + BC6HRGBFloat = 0x00000037, + BC7RGBAUnorm = 0x00000038, + BC7RGBAUnormSrgb = 0x00000039, + ETC2RGB8Unorm = 0x0000003A, + ETC2RGB8UnormSrgb = 0x0000003B, + ETC2RGB8A1Unorm = 0x0000003C, + ETC2RGB8A1UnormSrgb = 0x0000003D, + ETC2RGBA8Unorm = 0x0000003E, + ETC2RGBA8UnormSrgb = 0x0000003F, + EACR11Unorm = 0x00000040, + EACR11Snorm = 0x00000041, + EACRG11Unorm = 0x00000042, + EACRG11Snorm = 0x00000043, + ASTC4x4Unorm = 0x00000044, + ASTC4x4UnormSrgb = 0x00000045, + ASTC5x4Unorm = 0x00000046, + ASTC5x4UnormSrgb = 0x00000047, + ASTC5x5Unorm = 0x00000048, + ASTC5x5UnormSrgb = 0x00000049, + ASTC6x5Unorm = 0x0000004A, + ASTC6x5UnormSrgb = 0x0000004B, + ASTC6x6Unorm = 0x0000004C, + ASTC6x6UnormSrgb = 0x0000004D, + ASTC8x5Unorm = 0x0000004E, + ASTC8x5UnormSrgb = 0x0000004F, + ASTC8x6Unorm = 0x00000050, + ASTC8x6UnormSrgb = 0x00000051, + ASTC8x8Unorm = 0x00000052, + ASTC8x8UnormSrgb = 0x00000053, + ASTC10x5Unorm = 0x00000054, + ASTC10x5UnormSrgb = 0x00000055, + ASTC10x6Unorm = 0x00000056, + ASTC10x6UnormSrgb = 0x00000057, + ASTC10x8Unorm = 0x00000058, + ASTC10x8UnormSrgb = 0x00000059, + ASTC10x10Unorm = 0x0000005A, + ASTC10x10UnormSrgb = 0x0000005B, + ASTC12x10Unorm = 0x0000005C, + ASTC12x10UnormSrgb = 0x0000005D, + ASTC12x12Unorm = 0x0000005E, + ASTC12x12UnormSrgb = 0x0000005F, +} + +TextureSampleType :: enum i32 { + Undefined = 0x00000000, + Float = 0x00000001, + UnfilterableFloat = 0x00000002, + Depth = 0x00000003, + Sint = 0x00000004, + Uint = 0x00000005, +} + +TextureViewDimension :: enum i32 { + Undefined = 0x00000000, + _1D = 0x00000001, + _2D = 0x00000002, + _2DArray = 0x00000003, + Cube = 0x00000004, + CubeArray = 0x00000005, + _3D = 0x00000006, +} + +VertexFormat :: enum i32 { + Undefined = 0x00000000, + Uint8x2 = 0x00000001, + Uint8x4 = 0x00000002, + Sint8x2 = 0x00000003, + Sint8x4 = 0x00000004, + Unorm8x2 = 0x00000005, + Unorm8x4 = 0x00000006, + Snorm8x2 = 0x00000007, + Snorm8x4 = 0x00000008, + Uint16x2 = 0x00000009, + Uint16x4 = 0x0000000A, + Sint16x2 = 0x0000000B, + Sint16x4 = 0x0000000C, + Unorm16x2 = 0x0000000D, + Unorm16x4 = 0x0000000E, + Snorm16x2 = 0x0000000F, + Snorm16x4 = 0x00000010, + Float16x2 = 0x00000011, + Float16x4 = 0x00000012, + Float32 = 0x00000013, + Float32x2 = 0x00000014, + Float32x3 = 0x00000015, + Float32x4 = 0x00000016, + Uint32 = 0x00000017, + Uint32x2 = 0x00000018, + Uint32x3 = 0x00000019, + Uint32x4 = 0x0000001A, + Sint32 = 0x0000001B, + Sint32x2 = 0x0000001C, + Sint32x3 = 0x0000001D, + Sint32x4 = 0x0000001E, +} + +VertexStepMode :: enum i32 { + Vertex = 0x00000000, + Instance = 0x00000001, + VertexBufferNotUsed = 0x00000002, +} + +// WGSLFeatureName :: enum i32 { +// Undefined = 0x00000000, +// ReadonlyAndReadwriteStorageTextures = 0x00000001, +// Packed4x8IntegerDotProduct = 0x00000002, +// UnrestrictedPointerParameters = 0x00000003, +// PointerCompositeAccess = 0x00000004, +// } + +BufferUsage :: enum i32 { + MapRead = 0x00000000, + MapWrite = 0x00000001, + CopySrc = 0x00000002, + CopyDst = 0x00000003, + Index = 0x00000004, + Vertex = 0x00000005, + Uniform = 0x00000006, + Storage = 0x00000007, + Indirect = 0x00000008, + QueryResolve = 0x00000009, +} +BufferUsageFlags :: bit_set[BufferUsage; Flags] + +ColorWriteMask :: enum i32 { + Red = 0x00000000, + Green = 0x00000001, + Blue = 0x00000002, + Alpha = 0x00000003, +} +ColorWriteMaskFlags :: bit_set[ColorWriteMask; Flags] +ColorWriteMaskFlags_All :: ColorWriteMaskFlags{ .Red, .Green, .Blue, .Alpha } + +MapMode :: enum i32 { + Read = 0x00000000, + Write = 0x00000001, +} +MapModeFlags :: bit_set[MapMode; Flags] + +ShaderStage :: enum i32 { + Vertex = 0x00000000, + Fragment = 0x00000001, + Compute = 0x00000002, +} +ShaderStageFlags :: bit_set[ShaderStage; Flags] + +TextureUsage :: enum i32 { + CopySrc = 0x00000000, + CopyDst = 0x00000001, + TextureBinding = 0x00000002, + StorageBinding = 0x00000003, + RenderAttachment = 0x00000004, +} +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) + +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) + +ChainedStruct :: struct { + next: ^ChainedStruct, + sType: SType, +} + +ChainedStructOut :: struct { + next: ^ChainedStructOut, + sType: SType, +} + +// AdapterInfo :: struct { +// next: ^ChainedStructOut, +// vendor: cstring, +// architecture: cstring, +// device: cstring, +// description: cstring, +// backendType: BackendType, +// adapterType: AdapterType, +// vendorID: u32, +// deviceID: u32, +// } + +AdapterProperties :: struct { + nextInChain: ^ChainedStructOut, + vendorID: u32, + vendorName: cstring, + architecture: cstring, + deviceID: u32, + name: cstring, + driverDescription: cstring, + adapterType: AdapterType, + backendType: BackendType, +} + +BindGroupEntry :: struct { + nextInChain: ^ChainedStruct, + binding: u32, + /* NULLABLE */ buffer: Buffer, + offset: u64, + size: u64, + /* NULLABLE */ sampler: Sampler, + /* NULLABLE */ textureView: TextureView, +} + +BlendComponent :: struct { + operation: BlendOperation, + srcFactor: BlendFactor, + dstFactor: BlendFactor, +} + +BufferBindingLayout :: struct { + nextInChain: ^ChainedStruct, + type: BufferBindingType, + hasDynamicOffset: b32, + minBindingSize: u64, +} + +BufferDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + usage: BufferUsageFlags, + size: u64, + mappedAtCreation: b32, +} + +Color :: struct { + r: f64, + g: f64, + b: f64, + a: f64, +} + +CommandBufferDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, +} + +CommandEncoderDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, +} + +CompilationMessage :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ message: cstring, + type: CompilationMessageType, + lineNum: u64, + linePos: u64, + offset: u64, + length: u64, + utf16LinePos: u64, + utf16Offset: u64, + utf16Length: u64, +} + +ComputePassTimestampWrites :: struct { + querySet: QuerySet, + beginningOfPassWriteIndex: u32, + endOfPassWriteIndex: u32, +} + +ConstantEntry :: struct { + nextInChain: ^ChainedStruct, + key: cstring, + value: f64, +} + +Extent3D :: struct { + width: u32, + height: u32, + depthOrArrayLayers: u32, +} + +InstanceDescriptor :: struct { + nextInChain: ^ChainedStruct, +} + +Limits :: struct { + maxTextureDimension1D: u32, + maxTextureDimension2D: u32, + maxTextureDimension3D: u32, + maxTextureArrayLayers: u32, + maxBindGroups: u32, + maxBindGroupsPlusVertexBuffers: u32, + maxBindingsPerBindGroup: u32, + maxDynamicUniformBuffersPerPipelineLayout: u32, + maxDynamicStorageBuffersPerPipelineLayout: u32, + maxSampledTexturesPerShaderStage: u32, + maxSamplersPerShaderStage: u32, + maxStorageBuffersPerShaderStage: u32, + maxStorageTexturesPerShaderStage: u32, + maxUniformBuffersPerShaderStage: u32, + maxUniformBufferBindingSize: u64, + maxStorageBufferBindingSize: u64, + minUniformBufferOffsetAlignment: u32, + minStorageBufferOffsetAlignment: u32, + maxVertexBuffers: u32, + maxBufferSize: u64, + maxVertexAttributes: u32, + maxVertexBufferArrayStride: u32, + maxInterStageShaderComponents: u32, + maxInterStageShaderVariables: u32, + maxColorAttachments: u32, + maxColorAttachmentBytesPerSample: u32, + maxComputeWorkgroupStorageSize: u32, + maxComputeInvocationsPerWorkgroup: u32, + maxComputeWorkgroupSizeX: u32, + maxComputeWorkgroupSizeY: u32, + maxComputeWorkgroupSizeZ: u32, + maxComputeWorkgroupsPerDimension: u32, +} + +MultisampleState :: struct { + nextInChain: ^ChainedStruct, + count: u32, + mask: u32, + alphaToCoverageEnabled: b32, +} + +Origin3D :: struct { + x: u32, + y: u32, + z: u32, +} + +PipelineLayoutDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + bindGroupLayoutCount: uint, + bindGroupLayouts: [^]BindGroupLayout `fmt:"v,bindGroupLayoutCount"`, +} + +PrimitiveDepthClipControl :: struct { + using chain: ChainedStruct, + unclippedDepth: b32, +} + +PrimitiveState :: struct { + nextInChain: ^ChainedStruct, + topology: PrimitiveTopology, + stripIndexFormat: IndexFormat, + frontFace: FrontFace, + cullMode: CullMode, +} + +QuerySetDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + type: QueryType, + count: u32, +} + +QueueDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, +} + +RenderBundleDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, +} + +RenderBundleEncoderDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + colorFormatCount: uint, + colorFormats: [^]TextureFormat `fmt:"v,colorFormatCount"`, + depthStencilFormat: TextureFormat, + sampleCount: u32, + depthReadOnly: b32, + stencilReadOnly: b32, +} + +RenderPassDepthStencilAttachment :: struct { + view: TextureView, + depthLoadOp: LoadOp, + depthStoreOp: StoreOp, + depthClearValue: f32, + depthReadOnly: b32, + stencilLoadOp: LoadOp, + stencilStoreOp: StoreOp, + stencilClearValue: u32, + stencilReadOnly: b32, +} + +RenderPassDescriptorMaxDrawCount :: struct { + using chain: ChainedStruct, + maxDrawCount: u64, +} + +RenderPassTimestampWrites :: struct { + querySet: QuerySet, + beginningOfPassWriteIndex: u32, + endOfPassWriteIndex: u32, +} + +RequestAdapterOptions :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ compatibleSurface: Surface, + powerPreference: PowerPreference, + backendType: BackendType, + forceFallbackAdapter: b32, +} + +SamplerBindingLayout :: struct { + nextInChain: ^ChainedStruct, + type: SamplerBindingType, +} + +SamplerDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + addressModeU: AddressMode, + addressModeV: AddressMode, + addressModeW: AddressMode, + magFilter: FilterMode, + minFilter: FilterMode, + mipmapFilter: MipmapFilterMode, + lodMinClamp: f32, + lodMaxClamp: f32, + compare: CompareFunction, + maxAnisotropy: u16, +} + +ShaderModuleCompilationHint :: struct { + nextInChain: ^ChainedStruct, + entryPoint: cstring, + layout: PipelineLayout, +} + +ShaderModuleSPIRVDescriptor :: struct { + using chain: ChainedStruct, + codeSize: u32, + code: /* const */ [^]u32 `fmt:"v,codeSize"`, +} + +ShaderModuleWGSLDescriptor :: struct { + using chain: ChainedStruct, + code: cstring, +} + +StencilFaceState :: struct { + compare: CompareFunction, + failOp: StencilOperation, + depthFailOp: StencilOperation, + passOp: StencilOperation, +} + +StorageTextureBindingLayout :: struct { + nextInChain: ^ChainedStruct, + access: StorageTextureAccess, + format: TextureFormat, + viewDimension: TextureViewDimension, +} + +SurfaceCapabilities :: struct { + nextInChain: ^ChainedStructOut, + formatCount: uint, + formats: /* const */ [^]TextureFormat `fmt:"v,formatCount"`, + presentModeCount: uint, + presentModes: /* const */ [^]PresentMode `fmt:"v,presentModeCount"`, + alphaModeCount: uint, + alphaModes: /* const */ [^]CompositeAlphaMode `fmt:"v,alphaModeCount"`, +} + +SurfaceConfiguration :: struct { + nextInChain: ^ChainedStruct, + device: Device, + format: TextureFormat, + usage: TextureUsageFlags, + viewFormatCount: uint, + viewFormats: [^]TextureFormat `fmt:"v,viewFormatCount"`, + alphaMode: CompositeAlphaMode, + width: u32, + height: u32, + presentMode: PresentMode, +} + +SurfaceDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, +} + +SurfaceDescriptorFromAndroidNativeWindow :: struct { + using chain: ChainedStruct, + window: rawptr, +} + +SurfaceDescriptorFromCanvasHTMLSelector :: struct { + using chain: ChainedStruct, + selector: cstring, +} + +SurfaceDescriptorFromMetalLayer :: struct { + using chain: ChainedStruct, + layer: rawptr, +} + +SurfaceDescriptorFromWaylandSurface :: struct { + using chain: ChainedStruct, + display: rawptr, + surface: rawptr, +} + +SurfaceDescriptorFromWindowsHWND :: struct { + using chain: ChainedStruct, + hinstance: rawptr, + hwnd: rawptr, +} + +SurfaceDescriptorFromXcbWindow :: struct { + using chain: ChainedStruct, + connection: rawptr, + window: u32, +} + +SurfaceDescriptorFromXlibWindow :: struct { + using chain: ChainedStruct, + display: rawptr, + window: u64, +} + +SurfaceTexture :: struct { + texture: Texture, + suboptimal: b32, + status: SurfaceGetCurrentTextureStatus, +} + +TextureBindingLayout :: struct { + nextInChain: ^ChainedStruct, + sampleType: TextureSampleType, + viewDimension: TextureViewDimension, + multisampled: b32, +} + +TextureDataLayout :: struct { + nextInChain: ^ChainedStruct, + offset: u64, + bytesPerRow: u32, + rowsPerImage: u32, +} + +TextureViewDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + format: TextureFormat, + dimension: TextureViewDimension, + baseMipLevel: u32, + mipLevelCount: u32, + baseArrayLayer: u32, + arrayLayerCount: u32, + aspect: TextureAspect, +} + +VertexAttribute :: struct { + format: VertexFormat, + offset: u64, + shaderLocation: u32, +} + +BindGroupDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + layout: BindGroupLayout, + entryCount: uint, + entries: [^]BindGroupEntry `fmt:"v,entryCount"`, +} + +BindGroupLayoutEntry :: struct { + nextInChain: ^ChainedStruct, + binding: u32, + visibility: ShaderStageFlags, + buffer: BufferBindingLayout, + sampler: SamplerBindingLayout, + texture: TextureBindingLayout, + storageTexture: StorageTextureBindingLayout, +} + +BlendState :: struct { + color: BlendComponent, + alpha: BlendComponent, +} + +CompilationInfo :: struct { + nextInChain: ^ChainedStruct, + messageCount: uint, + messages: [^]CompilationMessage `fmt:"v,messageCount"`, +} + +ComputePassDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + /* NULLABLE */ timestampWrites: /* const */ ^ComputePassTimestampWrites, +} + +DepthStencilState :: struct { + nextInChain: ^ChainedStruct, + format: TextureFormat, + depthWriteEnabled: b32, + depthCompare: CompareFunction, + stencilFront: StencilFaceState, + stencilBack: StencilFaceState, + stencilReadMask: u32, + stencilWriteMask: u32, + depthBias: i32, + depthBiasSlopeScale: f32, + depthBiasClamp: f32, +} + +ImageCopyBuffer :: struct { + nextInChain: ^ChainedStruct, + layout: TextureDataLayout, + buffer: Buffer, +} + +ImageCopyTexture :: struct { + nextInChain: ^ChainedStruct, + texture: Texture, + mipLevel: u32, + origin: Origin3D, + aspect: TextureAspect, +} + +ProgrammableStageDescriptor :: struct { + nextInChain: ^ChainedStruct, + module: ShaderModule, + /* NULLABLE */ entryPoint: cstring, + constantCount: uint, + constants: [^]ConstantEntry `fmt:"v,constantCount"`, +} + +RenderPassColorAttachment :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ view: TextureView, + // depthSlice: u32, + /* NULLABLE */ resolveTarget: TextureView, + loadOp: LoadOp, + storeOp: StoreOp, + clearValue: Color, +} + +RequiredLimits :: struct { + nextInChain: ^ChainedStruct, + limits: Limits, +} + +ShaderModuleDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + hintCount: uint, + hints: [^]ShaderModuleCompilationHint `fmt:"v,hintCount"`, +} + +SupportedLimits :: struct { + nextInChain: ^ChainedStructOut, + limits: Limits, +} + +TextureDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + usage: TextureUsageFlags, + dimension: TextureDimension, + size: Extent3D, + format: TextureFormat, + mipLevelCount: u32, + sampleCount: u32, + viewFormatCount: uint, + viewFormats: [^]TextureFormat `fmt:"v,viewFormatCount"`, +} + +VertexBufferLayout :: struct { + arrayStride: u64, + stepMode: VertexStepMode, + attributeCount: uint, + attributes: [^]VertexAttribute `fmt:"v,attributeCount"`, +} + +BindGroupLayoutDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + entryCount: uint, + entries: [^]BindGroupLayoutEntry `fmt:"v,entryCount"`, +} + +ColorTargetState :: struct { + nextInChain: ^ChainedStruct, + format: TextureFormat, + /* NULLABLE */ blend: /* const */ ^BlendState, + writeMask: ColorWriteMaskFlags, +} + +ComputePipelineDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + /* NULLABLE */ layout: PipelineLayout, + compute: ProgrammableStageDescriptor, +} + +DeviceDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + requiredFeatureCount: uint, + requiredFeatures: [^]FeatureName `fmt:"v,requiredFeatureCount"`, + /* NULLABLE */ requiredLimits: /* const */ ^RequiredLimits, + defaultQueue: QueueDescriptor, + deviceLostCallback: DeviceLostCallback, + deviceLostUserdata: rawptr, +} + +RenderPassDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + colorAttachmentCount: uint, + colorAttachments: [^]RenderPassColorAttachment `fmt:"v,colorAttachmentCount"`, + /* NULLABLE */ depthStencilAttachment: /* const */ ^RenderPassDepthStencilAttachment, + /* NULLABLE */ occlusionQuerySet: QuerySet, + /* NULLABLE */ timestampWrites: /* const */ ^RenderPassTimestampWrites, +} + +VertexState :: struct { + nextInChain: ^ChainedStruct, + module: ShaderModule, + /* NULLABLE */ entryPoint: cstring, + constantCount: uint, + constants: [^]ConstantEntry `fmt:"v,constantCount"`, + bufferCount: uint, + buffers: [^]VertexBufferLayout `fmt:"v,bufferCount"`, +} + +FragmentState :: struct { + nextInChain: ^ChainedStruct, + module: ShaderModule, + /* NULLABLE */ entryPoint: cstring, + constantCount: uint, + constants: [^]ConstantEntry `fmt:"v,constantCount"`, + targetCount: uint, + targets: [^]ColorTargetState `fmt:"v,targetCount"`, +} + +RenderPipelineDescriptor :: struct { + nextInChain: ^ChainedStruct, + /* NULLABLE */ label: cstring, + /* NULLABLE */ layout: PipelineLayout, + vertex: VertexState, + primitive: PrimitiveState, + /* NULLABLE */ depthStencil: /* const */ ^DepthStencilState, + multisample: MultisampleState, + /* NULLABLE */ fragment: /* const */ ^FragmentState, +} + +@(link_prefix="wgpu", default_calling_convention="c") +foreign libwgpu { + CreateInstance :: proc(/* NULLABLE */ descriptor: /* const */ ^InstanceDescriptor = nil) -> Instance --- + GetProcAddress :: proc(device: Device, procName: cstring) -> Proc --- + + // Methods of Adapter + @(link_name="wgpuAdapterEnumerateFeatures") + RawAdapterEnumerateFeatures :: proc(adapter: Adapter, features: [^]FeatureName) -> uint --- + @(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) --- + + // Methods of BindGroup + BindGroupSetLabel :: proc(bindGroup: BindGroup, label: cstring) --- + BindGroupReference :: proc(bindGroup: BindGroup) --- + BindGroupRelease :: proc(bindGroup: BindGroup) --- + + // Methods of BindGroupLayout + BindGroupLayoutSetLabel :: proc(bindGroupLayout: BindGroupLayout, label: cstring) --- + BindGroupLayoutReference :: proc(bindGroupLayout: BindGroupLayout) --- + BindGroupLayoutRelease :: proc(bindGroupLayout: BindGroupLayout) --- + + // Methods of Buffer + BufferDestroy :: proc(buffer: Buffer) --- + @(link_name="wgpuBufferGetConstMappedRange") + RawBufferGetConstMappedRange :: proc(buffer: Buffer, offset: uint, size: uint) -> /* const */ rawptr --- + BufferGetMapState :: proc(buffer: Buffer) -> BufferMapState --- + @(link_name="wgpuBufferGetMappedRange") + RawBufferGetMappedRange :: proc(buffer: Buffer, offset: uint, size: uint) -> rawptr --- + BufferGetSize :: proc(buffer: Buffer) -> u64 --- + BufferGetUsage :: proc(buffer: Buffer) -> BufferUsageFlags --- + BufferMapAsync :: proc(buffer: Buffer, mode: MapModeFlags, offset: uint, size: uint, callback: BufferMapAsyncCallback, /* NULLABLE */ userdata: rawptr = nil) --- + BufferSetLabel :: proc(buffer: Buffer, label: cstring) --- + BufferUnmap :: proc(buffer: Buffer) --- + BufferReference :: proc(buffer: Buffer) --- + BufferRelease :: proc(buffer: Buffer) --- + + // Methods of CommandBuffer + CommandBufferSetLabel :: proc(commandBuffer: CommandBuffer, label: cstring) --- + CommandBufferReference :: proc(commandBuffer: CommandBuffer) --- + CommandBufferRelease :: proc(commandBuffer: CommandBuffer) --- + + // Methods of CommandEncoder + CommandEncoderBeginComputePass :: proc(commandEncoder: CommandEncoder, /* NULLABLE */ descriptor: /* const */ ^ComputePassDescriptor = nil) -> ComputePassEncoder --- + CommandEncoderBeginRenderPass :: proc(commandEncoder: CommandEncoder, descriptor: /* const */ ^RenderPassDescriptor) -> RenderPassEncoder --- + CommandEncoderClearBuffer :: proc(commandEncoder: CommandEncoder, buffer: Buffer, offset: u64, size: u64) --- + CommandEncoderCopyBufferToBuffer :: proc(commandEncoder: CommandEncoder, source: Buffer, sourceOffset: u64, destination: Buffer, destinationOffset: u64, size: u64) --- + CommandEncoderCopyBufferToTexture :: proc(commandEncoder: CommandEncoder, source: /* const */ ^ImageCopyBuffer, destination: /* const */ ^ImageCopyTexture, copySize: /* const */ ^Extent3D) --- + CommandEncoderCopyTextureToBuffer :: proc(commandEncoder: CommandEncoder, source: /* const */ ^ImageCopyTexture, destination: /* const */ ^ImageCopyBuffer, copySize: /* const */ ^Extent3D) --- + CommandEncoderCopyTextureToTexture :: proc(commandEncoder: CommandEncoder, source: /* const */ ^ImageCopyTexture, destination: /* const */ ^ImageCopyTexture, copySize: /* const */ ^Extent3D) --- + CommandEncoderFinish :: proc(commandEncoder: CommandEncoder, /* NULLABLE */ descriptor: /* const */ ^CommandBufferDescriptor = nil) -> CommandBuffer --- + CommandEncoderInsertDebugMarker :: proc(commandEncoder: CommandEncoder, markerLabel: cstring) --- + CommandEncoderPopDebugGroup :: proc(commandEncoder: CommandEncoder) --- + CommandEncoderPushDebugGroup :: proc(commandEncoder: CommandEncoder, groupLabel: cstring) --- + CommandEncoderResolveQuerySet :: proc(commandEncoder: CommandEncoder, querySet: QuerySet, firstQuery: u32, queryCount: u32, destination: Buffer, destinationOffset: u64) --- + CommandEncoderSetLabel :: proc(commandEncoder: CommandEncoder, label: cstring) --- + CommandEncoderWriteTimestamp :: proc(commandEncoder: CommandEncoder, querySet: QuerySet, queryIndex: u32) --- + CommandEncoderReference :: proc(commandEncoder: CommandEncoder) --- + CommandEncoderRelease :: proc(commandEncoder: CommandEncoder) --- + + // Methods of ComputePassEncoder + ComputePassEncoderDispatchWorkgroups :: proc(computePassEncoder: ComputePassEncoder, workgroupCountX: u32, workgroupCountY: u32, workgroupCountZ: u32) --- + ComputePassEncoderDispatchWorkgroupsIndirect :: proc(computePassEncoder: ComputePassEncoder, indirectBuffer: Buffer, indirectOffset: u64) --- + ComputePassEncoderEnd :: proc(computePassEncoder: ComputePassEncoder) --- + ComputePassEncoderInsertDebugMarker :: proc(computePassEncoder: ComputePassEncoder, markerLabel: cstring) --- + ComputePassEncoderPopDebugGroup :: proc(computePassEncoder: ComputePassEncoder) --- + ComputePassEncoderPushDebugGroup :: proc(computePassEncoder: ComputePassEncoder, groupLabel: cstring) --- + @(link_name="wgpuComputePassEncoderSetBindGroup") + RawComputePassEncoderSetBindGroup :: proc(computePassEncoder: ComputePassEncoder, groupIndex: u32, /* NULLABLE */ group: BindGroup, dynamicOffsetCount: uint, dynamicOffsets: [^]u32) --- + ComputePassEncoderSetLabel :: proc(computePassEncoder: ComputePassEncoder, label: cstring) --- + ComputePassEncoderSetPipeline :: proc(computePassEncoder: ComputePassEncoder, pipeline: ComputePipeline) --- + ComputePassEncoderReference :: proc(computePassEncoder: ComputePassEncoder) --- + ComputePassEncoderRelease :: proc(computePassEncoder: ComputePassEncoder) --- + + // Methods of ComputePipeline + ComputePipelineGetBindGroupLayout :: proc(computePipeline: ComputePipeline, groupIndex: u32) -> BindGroupLayout --- + ComputePipelineSetLabel :: proc(computePipeline: ComputePipeline, label: cstring) --- + ComputePipelineReference :: proc(computePipeline: ComputePipeline) --- + ComputePipelineRelease :: proc(computePipeline: ComputePipeline) --- + + // Methods of Device + DeviceCreateBindGroup :: proc(device: Device, descriptor: /* const */ ^BindGroupDescriptor) -> BindGroup --- + DeviceCreateBindGroupLayout :: proc(device: Device, descriptor: /* const */ ^BindGroupLayoutDescriptor) -> BindGroupLayout --- + DeviceCreateBuffer :: proc(device: Device, descriptor: /* const */ ^BufferDescriptor) -> Buffer --- + DeviceCreateCommandEncoder :: proc(device: Device, /* NULLABLE */ descriptor: /* const */ ^CommandEncoderDescriptor = nil) -> CommandEncoder --- + DeviceCreateComputePipeline :: proc(device: Device, descriptor: /* const */ ^ComputePipelineDescriptor) -> ComputePipeline --- + DeviceCreateComputePipelineAsync :: proc(device: Device, descriptor: /* const */ ^ComputePipelineDescriptor, callback: DeviceCreateComputePipelineAsyncCallback, /* NULLABLE */ userdata: rawptr = nil) --- + DeviceCreatePipelineLayout :: proc(device: Device, descriptor: /* const */ ^PipelineLayoutDescriptor) -> PipelineLayout --- + DeviceCreateQuerySet :: proc(device: Device, descriptor: /* const */ ^QuerySetDescriptor) -> QuerySet --- + DeviceCreateRenderBundleEncoder :: proc(device: Device, descriptor: /* const */ ^RenderBundleEncoderDescriptor) -> RenderBundleEncoder --- + DeviceCreateRenderPipeline :: proc(device: Device, descriptor: /* const */ ^RenderPipelineDescriptor) -> RenderPipeline --- + DeviceCreateRenderPipelineAsync :: proc(device: Device, descriptor: /* const */ ^RenderPipelineDescriptor, callback: DeviceCreateRenderPipelineAsyncCallback, /* NULLABLE */ userdata: rawptr = nil) --- + DeviceCreateSampler :: proc(device: Device, /* NULLABLE */ descriptor: /* const */ ^SamplerDescriptor = nil) -> Sampler --- + DeviceCreateShaderModule :: proc(device: Device, descriptor: /* const */ ^ShaderModuleDescriptor) -> ShaderModule --- + DeviceCreateTexture :: proc(device: Device, descriptor: /* const */ ^TextureDescriptor) -> Texture --- + DeviceDestroy :: proc(device: Device) --- + @(link_name="wgpuDeviceEnumerateFeatures") + RawDeviceEnumerateFeatures :: proc(device: Device, features: ^FeatureName) -> uint --- + @(link_name="wgpuDeviceGetLimits") + RawDeviceGetLimits :: proc(device: Device, limits: ^SupportedLimits) -> b32 --- + DeviceGetQueue :: proc(device: Device) -> Queue --- + DeviceHasFeature :: proc(device: Device, feature: FeatureName) -> b32 --- + 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 --- + InstanceProcessEvents :: proc(instance: Instance) --- + InstanceRequestAdapter :: proc(instance: Instance, /* NULLABLE */ options: /* const */ ^RequestAdapterOptions, callback: InstanceRequestAdapterCallback, /* NULLABLE */ userdata: rawptr = nil) --- + InstanceReference :: proc(instance: Instance) --- + InstanceRelease :: proc(instance: Instance) --- + + // Methods of PipelineLayout + PipelineLayoutSetLabel :: proc(pipelineLayout: PipelineLayout, label: cstring) --- + PipelineLayoutReference :: proc(pipelineLayout: PipelineLayout) --- + PipelineLayoutRelease :: proc(pipelineLayout: PipelineLayout) --- + + // Methods of QuerySet + QuerySetDestroy :: proc(querySet: QuerySet) --- + QuerySetGetCount :: proc(querySet: QuerySet) -> u32 --- + QuerySetGetType :: proc(querySet: QuerySet) -> QueryType --- + QuerySetSetLabel :: proc(querySet: QuerySet, label: cstring) --- + QuerySetReference :: proc(querySet: QuerySet) --- + QuerySetRelease :: proc(querySet: QuerySet) --- + + // Methods of Queue + QueueOnSubmittedWorkDone :: proc(queue: Queue, callback: QueueOnSubmittedWorkDoneCallback, /* NULLABLE */ userdata: rawptr = nil) --- + QueueSetLabel :: proc(queue: Queue, label: cstring) --- + @(link_name="wgpuQueueSubmit") + RawQueueSubmit :: proc(queue: Queue, commandCount: uint, commands: [^]CommandBuffer) --- + QueueWriteBuffer :: proc(queue: Queue, buffer: Buffer, bufferOffset: u64, data: /* const */ rawptr, size: uint) --- + QueueWriteTexture :: proc(queue: Queue, destination: /* const */ ^ImageCopyTexture, data: /* const */ rawptr, dataSize: uint, dataLayout: /* const */ ^TextureDataLayout, writeSize: /* const */ ^Extent3D) --- + QueueReference :: proc(queue: Queue) --- + QueueRelease :: proc(queue: Queue) --- + + // Methods of RenderBundle + RenderBundleSetLabel :: proc(renderBundle: RenderBundle, label: cstring) --- + RenderBundleReference :: proc(renderBundle: RenderBundle) --- + RenderBundleRelease :: proc(renderBundle: RenderBundle) --- + + // Methods of RenderBundleEncoder + RenderBundleEncoderDraw :: proc(renderBundleEncoder: RenderBundleEncoder, vertexCount: u32, instanceCount: u32, firstVertex: u32, firstInstance: u32) --- + RenderBundleEncoderDrawIndexed :: proc(renderBundleEncoder: RenderBundleEncoder, indexCount: u32, instanceCount: u32, firstIndex: u32, baseVertex: i32, firstInstance: u32) --- + RenderBundleEncoderDrawIndexedIndirect :: proc(renderBundleEncoder: RenderBundleEncoder, indirectBuffer: Buffer, indirectOffset: u64) --- + RenderBundleEncoderDrawIndirect :: proc(renderBundleEncoder: RenderBundleEncoder, indirectBuffer: Buffer, indirectOffset: u64) --- + RenderBundleEncoderFinish :: proc(renderBundleEncoder: RenderBundleEncoder, /* NULLABLE */ descriptor: /* const */ ^RenderBundleDescriptor = nil) -> RenderBundle --- + RenderBundleEncoderInsertDebugMarker :: proc(renderBundleEncoder: RenderBundleEncoder, markerLabel: cstring) --- + RenderBundleEncoderPopDebugGroup :: proc(renderBundleEncoder: RenderBundleEncoder) --- + RenderBundleEncoderPushDebugGroup :: proc(renderBundleEncoder: RenderBundleEncoder, groupLabel: cstring) --- + @(link_name="wgpuRenderBundleEncoderSetBindGroup") + RawRenderBundleEncoderSetBindGroup :: proc(renderBundleEncoder: RenderBundleEncoder, groupIndex: u32, /* NULLABLE */ group: BindGroup, dynamicOffsetCount: uint, dynamicOffsets: [^]u32) --- + RenderBundleEncoderSetIndexBuffer :: proc(renderBundleEncoder: RenderBundleEncoder, buffer: Buffer, format: IndexFormat, offset: u64, size: u64) --- + RenderBundleEncoderSetLabel :: proc(renderBundleEncoder: RenderBundleEncoder, label: cstring) --- + RenderBundleEncoderSetPipeline :: proc(renderBundleEncoder: RenderBundleEncoder, pipeline: RenderPipeline) --- + RenderBundleEncoderSetVertexBuffer :: proc(renderBundleEncoder: RenderBundleEncoder, slot: u32, /* NULLABLE */ buffer: Buffer, offset: u64, size: u64) --- + RenderBundleEncoderReference :: proc(renderBundleEncoder: RenderBundleEncoder) --- + RenderBundleEncoderRelease :: proc(renderBundleEncoder: RenderBundleEncoder) --- + + // Methods of RenderPassEncoder + RenderPassEncoderBeginOcclusionQuery :: proc(renderPassEncoder: RenderPassEncoder, queryIndex: u32) --- + RenderPassEncoderDraw :: proc(renderPassEncoder: RenderPassEncoder, vertexCount: u32, instanceCount: u32, firstVertex: u32, firstInstance: u32) --- + RenderPassEncoderDrawIndexed :: proc(renderPassEncoder: RenderPassEncoder, indexCount: u32, instanceCount: u32, firstIndex: u32, baseVertex: i32, firstInstance: u32) --- + RenderPassEncoderDrawIndexedIndirect :: proc(renderPassEncoder: RenderPassEncoder, indirectBuffer: Buffer, indirectOffset: u64) --- + RenderPassEncoderDrawIndirect :: proc(renderPassEncoder: RenderPassEncoder, indirectBuffer: Buffer, indirectOffset: u64) --- + RenderPassEncoderEnd :: proc(renderPassEncoder: RenderPassEncoder) --- + RenderPassEncoderEndOcclusionQuery :: proc(renderPassEncoder: RenderPassEncoder) --- + @(link_name="wgpuRenderPassEncoderExecuteBundles") + RawRenderPassEncoderExecuteBundles :: proc(renderPassEncoder: RenderPassEncoder, bundleCount: uint, bundles: [^]RenderBundle) --- + RenderPassEncoderInsertDebugMarker :: proc(renderPassEncoder: RenderPassEncoder, markerLabel: cstring) --- + RenderPassEncoderPopDebugGroup :: proc(renderPassEncoder: RenderPassEncoder) --- + RenderPassEncoderPushDebugGroup :: proc(renderPassEncoder: RenderPassEncoder, groupLabel: cstring) --- + @(link_name="wgpuRenderPassEncoderSetBindGroup") + RawRenderPassEncoderSetBindGroup :: proc(renderPassEncoder: RenderPassEncoder, groupIndex: u32, /* NULLABLE */ group: BindGroup, dynamicOffsetCount: uint, dynamicOffsets: [^]u32) --- + RenderPassEncoderSetBlendConstant :: proc(renderPassEncoder: RenderPassEncoder, color: /* const */ ^Color) --- + RenderPassEncoderSetIndexBuffer :: proc(renderPassEncoder: RenderPassEncoder, buffer: Buffer, format: IndexFormat, offset: u64, size: u64) --- + RenderPassEncoderSetLabel :: proc(renderPassEncoder: RenderPassEncoder, label: cstring) --- + RenderPassEncoderSetPipeline :: proc(renderPassEncoder: RenderPassEncoder, pipeline: RenderPipeline) --- + RenderPassEncoderSetScissorRect :: proc(renderPassEncoder: RenderPassEncoder, x: u32, y: u32, width: u32, height: u32) --- + RenderPassEncoderSetStencilReference :: proc(renderPassEncoder: RenderPassEncoder, reference: u32) --- + RenderPassEncoderSetVertexBuffer :: proc(renderPassEncoder: RenderPassEncoder, slot: u32, /* NULLABLE */ buffer: Buffer, offset: u64, size: u64) --- + RenderPassEncoderSetViewport :: proc(renderPassEncoder: RenderPassEncoder, x: f32, y: f32, width: f32, height: f32, minDepth: f32, maxDepth: f32) --- + RenderPassEncoderReference :: proc(renderPassEncoder: RenderPassEncoder) --- + RenderPassEncoderRelease :: proc(renderPassEncoder: RenderPassEncoder) --- + + // Methods of RenderPipeline + RenderPipelineGetBindGroupLayout :: proc(renderPipeline: RenderPipeline, groupIndex: u32) -> BindGroupLayout --- + RenderPipelineSetLabel :: proc(renderPipeline: RenderPipeline, label: cstring) --- + RenderPipelineReference :: proc(renderPipeline: RenderPipeline) --- + RenderPipelineRelease :: proc(renderPipeline: RenderPipeline) --- + + // Methods of Sampler + SamplerSetLabel :: proc(sampler: Sampler, label: cstring) --- + SamplerReference :: proc(sampler: Sampler) --- + SamplerRelease :: proc(sampler: Sampler) --- + + // Methods of ShaderModule + ShaderModuleGetCompilationInfo :: proc(shaderModule: ShaderModule, callback: ShaderModuleGetCompilationInfoCallback, /* NULLABLE */ userdata: rawptr = nil) --- + ShaderModuleSetLabel :: proc(shaderModule: ShaderModule, label: cstring) --- + ShaderModuleReference :: proc(shaderModule: ShaderModule) --- + ShaderModuleRelease :: proc(shaderModule: ShaderModule) --- + + // Methods of Surface + SurfaceConfigure :: proc(surface: Surface, config: /* const */ ^SurfaceConfiguration) --- + @(link_name="wgpuSurfaceGetCapabilities") + 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) --- + SurfaceUnconfigure :: proc(surface: Surface) --- + SurfaceReference :: proc(surface: Surface) --- + SurfaceRelease :: proc(surface: Surface) --- + + // Methods of SurfaceCapabilities + SurfaceCapabilitiesFreeMembers :: proc(surfaceCapabilities: SurfaceCapabilities) --- + + // Methods of Texture + TextureCreateView :: proc(texture: Texture, /* NULLABLE */ descriptor: /* const */ ^TextureViewDescriptor = nil) -> TextureView --- + TextureDestroy :: proc(texture: Texture) --- + TextureGetDepthOrArrayLayers :: proc(texture: Texture) -> u32 --- + TextureGetDimension :: proc(texture: Texture) -> TextureDimension --- + TextureGetFormat :: proc(texture: Texture) -> TextureFormat --- + TextureGetHeight :: proc(texture: Texture) -> u32 --- + TextureGetMipLevelCount :: proc(texture: Texture) -> u32 --- + TextureGetSampleCount :: proc(texture: Texture) -> u32 --- + TextureGetUsage :: proc(texture: Texture) -> TextureUsageFlags --- + TextureGetWidth :: proc(texture: Texture) -> u32 --- + TextureSetLabel :: proc(texture: Texture, label: cstring) --- + TextureReference :: proc(texture: Texture) --- + TextureRelease :: proc(texture: Texture) --- + + // Methods of TextureView + TextureViewSetLabel :: proc(textureView: TextureView, label: cstring) --- + TextureViewReference :: proc(textureView: TextureView) --- + TextureViewRelease :: proc(textureView: TextureView) --- +} + +// Wrappers of Adapter + +AdapterEnumerateFeatures :: proc(adapter: Adapter, allocator := context.allocator) -> []FeatureName { + count := RawAdapterEnumerateFeatures(adapter, nil) + features := make([]FeatureName, count, allocator) + RawAdapterEnumerateFeatures(adapter, raw_data(features)) + return features +} + +AdapterGetLimits :: proc(adapter: Adapter) -> (limits: SupportedLimits, ok: bool) { + ok = bool(RawAdapterGetLimits(adapter, &limits)) + return +} + +AdapterGetProperties :: proc(adapter: Adapter) -> (properties: AdapterProperties) { + RawAdapterGetProperties(adapter, &properties) + return +} + +// Wrappers of Buffer + +BufferGetConstMappedRange :: proc(buffer: Buffer, offset: uint, size: uint) -> []byte { + return ([^]byte)(RawBufferGetConstMappedRange(buffer, offset, size))[:size] +} + +BufferGetConstMappedRangeTyped :: proc(buffer: Buffer, offset: uint, $T: typeid) -> ^T + where !intrinsics.type_is_sliceable(T) { + + return (^T)(RawBufferGetConstMappedRange(buffer, 0, size_of(T))) +} + +BufferGetConstMappedRangeSlice :: proc(buffer: Buffer, offset: uint, length: uint, $T: typeid) -> []T { + return ([^]T)(RawBufferGetConstMappedRange(buffer, offset, size_of(T)*length))[:length] +} + +BufferGetMappedRange :: proc(buffer: Buffer, offset: uint, size: uint) -> []byte { + return ([^]byte)(RawBufferGetMappedRange(buffer, offset, size))[:size] +} + +BufferGetMappedRangeTyped :: proc(buffer: Buffer, offset: uint, $T: typeid) -> ^T + where !intrinsics.type_is_sliceable(T) { + + return (^T)(RawBufferGetMappedRange(buffer, offset, size_of(T))) +} + +BufferGetMappedRangeSlice :: proc(buffer: Buffer, offset: uint, $T: typeid, length: uint) -> []T { + return ([^]T)(RawBufferGetMappedRange(buffer, offset, size_of(T)*length))[:length] +} + +// Wrappers of ComputePassEncoder + +ComputePassEncoderSetBindGroup :: proc(computePassEncoder: ComputePassEncoder, groupIndex: u32, /* NULLABLE */ group: BindGroup, dynamicOffsets: []u32 = nil) { + RawComputePassEncoderSetBindGroup(computePassEncoder, groupIndex, group, len(dynamicOffsets), raw_data(dynamicOffsets)) +} + +// Wrappers of Device + +DeviceEnumerateFeatures :: proc(device: Device, allocator := context.allocator) -> []FeatureName { + count := RawDeviceEnumerateFeatures(device, nil) + features := make([]FeatureName, count, allocator) + RawDeviceEnumerateFeatures(device, raw_data(features)) + return features +} + +DeviceGetLimits :: proc(device: Device) -> (limits: SupportedLimits, ok: bool) { + ok = bool(RawDeviceGetLimits(device, &limits)) + return +} + +BufferWithDataDescriptor :: struct { + /* NULLABLE */ label: cstring, + usage: BufferUsageFlags, +} + +DeviceCreateBufferWithDataSlice :: proc(device: Device, descriptor: /* const */ ^BufferWithDataDescriptor, data: []$T) -> (buf: Buffer) { + size := u64(size_of(T) * len(data)) + buf = DeviceCreateBuffer(device, &{ + label = descriptor.label, + usage = descriptor.usage, + size = size, + mappedAtCreation = true, + }) + + mapping := BufferGetMappedRangeSlice(buf, 0, T, len(data)) + copy(mapping, data) + + BufferUnmap(buf) + return +} + +DeviceCreateBufferWithDataTyped :: proc(device: Device, descriptor: /* const */ ^BufferWithDataDescriptor, data: $T) -> (buf: Buffer) + where !intrinsics.type_is_sliceable(T) { + + buf = DeviceCreateBuffer(device, &{ + label = descriptor.label, + usage = descriptor.usage, + size = size_of(T), + mappedAtCreation = true, + }) + + mapping := BufferGetMappedRangeTyped(buf, 0, T) + mapping^ = data + + BufferUnmap(buf) + return +} + +DeviceCreateBufferWithData :: proc { + DeviceCreateBufferWithDataSlice, + DeviceCreateBufferWithDataTyped, +} + +// Wrappers of Queue + +QueueSubmit :: proc(queue: Queue, commands: []CommandBuffer) { + RawQueueSubmit(queue, len(commands), raw_data(commands)) +} + +// Wrappers of RenderBundleEncoder + +RenderBundleEncoderSetBindGroup :: proc(renderBundleEncoder: RenderBundleEncoder, groupIndex: u32, /* NULLABLE */ group: BindGroup, dynamicOffsets: []u32 = nil) { + RawRenderBundleEncoderSetBindGroup(renderBundleEncoder, groupIndex, group, len(dynamicOffsets), raw_data(dynamicOffsets)) +} + +// Wrappers of RenderPassEncoder + +RenderPassEncoderExecuteBundles :: proc(renderPassEncoder: RenderPassEncoder, bundles: []RenderBundle) { + RawRenderPassEncoderExecuteBundles(renderPassEncoder, len(bundles), raw_data(bundles)) +} + +RenderPassEncoderSetBindGroup :: proc(renderPassEncoder: RenderPassEncoder, groupIndex: u32, /* NULLABLE */ group: BindGroup, dynamicOffsets: []u32 = nil) { + RawRenderPassEncoderSetBindGroup(renderPassEncoder, groupIndex, group, len(dynamicOffsets), raw_data(dynamicOffsets)) +} + +// Wrappers of Surface + +SurfaceGetCapabilities :: proc(surface: Surface, adapter: Adapter) -> (capabilities: SurfaceCapabilities) { + RawSurfaceGetCapabilities(surface, adapter, &capabilities) + return +} + +SurfaceGetCurrentTexture :: proc(surface: Surface) -> (surface_texture: SurfaceTexture) { + RawSurfaceGetCurrentTexture(surface, &surface_texture) + return +} + +// WGPU Native bindings + +BINDINGS_VERSION :: [4]u8{0, 19, 4, 1} +BINDINGS_VERSION_STRING :: "0.19.4.1" + +when ODIN_OS != .JS { + @(private="file", init) + wgpu_native_version_check :: proc() { + v := (transmute([4]u8)GetVersion()).wzyx + + if v != BINDINGS_VERSION { + buf: [1024]byte + n := copy(buf[:], "wgpu-native version mismatch: ") + n += copy(buf[n:], "bindings are for version ") + n += copy(buf[n:], BINDINGS_VERSION_STRING) + n += copy(buf[n:], ", but a different version is linked") + panic(string(buf[:n])) + } + } + + @(link_prefix="wgpu") + foreign libwgpu { + @(link_name="wgpuGenerateReport") + RawGenerateReport :: proc(instance: Instance, report: ^GlobalReport) --- + @(link_name="wgpuInstanceEnumerateAdapters") + RawInstanceEnumerateAdapters :: proc(instance: Instance, /* NULLABLE */ options: /* const */ ^InstanceEnumerateAdapterOptions, adapters: [^]Adapter) -> uint --- + + @(link_name="wgpuQueueSubmitForIndex") + RawQueueSubmitForIndex :: proc(queue: Queue, commandCount: uint, commands: [^]CommandBuffer) -> SubmissionIndex --- + + // Returns true if the queue is empty, or false if there are more queue submissions still in flight. + DevicePoll :: proc(device: Device, wait: b32, /* NULLABLE */ wrappedSubmissionIndex: /* const */ ^WrappedSubmissionIndex = nil) -> b32 --- + + SetLogCallback :: proc(callback: LogCallback, userdata: rawptr) --- + + SetLogLevel :: proc(level: LogLevel) --- + + GetVersion :: proc() -> u32 --- + + RenderPassEncoderSetPushConstants :: proc(encoder: RenderPassEncoder, stages: ShaderStageFlags, offset: u32, sizeBytes: u32, data: rawptr) --- + + RenderPassEncoderMultiDrawIndirect :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count: u32) --- + RenderPassEncoderMultiDrawIndexedIndirect :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count: u32) --- + + RenderPassEncoderMultiDrawIndirectCount :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count_buffer: Buffer, count_buffer_offset: u64, max_count: u32) --- + RenderPassEncoderMultiDrawIndexedIndirectCount :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count_buffer: Buffer, count_buffer_offset: u64, max_count: u32) --- + + ComputePassEncoderBeginPipelineStatisticsQuery :: proc(computePassEncoder: ComputePassEncoder, querySet: QuerySet, queryIndex: u32) --- + ComputePassEncoderEndPipelineStatisticsQuery :: proc(computePassEncoder: ComputePassEncoder) --- + RenderPassEncoderBeginPipelineStatisticsQuery :: proc(renderPassEncoder: RenderPassEncoder, querySet: QuerySet, queryIndex: u32) --- + RenderPassEncoderEndPipelineStatisticsQuery :: proc(renderPassEncoder: RenderPassEncoder) --- + } + + GenerateReport :: proc(instance: Instance) -> (report: GlobalReport) { + RawGenerateReport(instance, &report) + return + } + + InstanceEnumerateAdapters :: proc(instance: Instance, options: ^InstanceEnumerateAdapterOptions = nil, allocator := context.allocator) -> (adapters: []Adapter) { + count := RawInstanceEnumerateAdapters(instance, options, nil) + adapters = make([]Adapter, count, allocator) + RawInstanceEnumerateAdapters(instance, options, raw_data(adapters)) + return + } + + QueueSubmitForIndex :: proc(queue: Queue, commands: []CommandBuffer) -> SubmissionIndex { + return RawQueueSubmitForIndex(queue, len(commands), raw_data(commands)) + } +} diff --git a/vendor/wgpu/wgpu_js.odin b/vendor/wgpu/wgpu_js.odin new file mode 100644 index 000000000..3c8375adb --- /dev/null +++ b/vendor/wgpu/wgpu_js.odin @@ -0,0 +1,27 @@ +package wgpu + +import "base:runtime" + +g_context: runtime.Context + +@(private="file", init) +wgpu_init_allocator :: proc() { + if g_context.allocator.procedure == nil { + g_context = runtime.default_context() + } +} + +@(private="file", export) +wgpu_alloc :: proc "contextless" (size: i32) -> [^]byte { + context = g_context + bytes, err := runtime.mem_alloc(int(size), 16) + assert(err == nil, "wgpu_alloc failed") + return raw_data(bytes) +} + +@(private="file", export) +wgpu_free :: proc "contextless" (ptr: rawptr) { + context = g_context + err := free(ptr) + assert(err == nil, "wgpu_free failed") +} diff --git a/vendor/wgpu/wgpu_native_types.odin b/vendor/wgpu/wgpu_native_types.odin new file mode 100644 index 000000000..2133fdd50 --- /dev/null +++ b/vendor/wgpu/wgpu_native_types.odin @@ -0,0 +1,212 @@ +package wgpu + +import "base:runtime" + +LogLevel :: enum i32 { + Off, + Error, + Warn, + Info, + Debug, + Trace, +} + +InstanceBackend :: enum i32 { + Vulkan, + GL, + Metal, + DX12, + DX11, + BrowserWebGPU, +} +InstanceBackendFlags :: bit_set[InstanceBackend; Flags] +InstanceBackendFlags_All :: InstanceBackendFlags{} +InstanceBackendFlags_Primary :: InstanceBackendFlags{ .Vulkan, .Metal, .DX12, .BrowserWebGPU } +InstanceBackendFlags_Secondary :: InstanceBackendFlags{ .GL, .DX11 } + +InstanceFlag :: enum i32 { + Debug, + Validation, + DiscardHalLabels, +} +InstanceFlags :: bit_set[InstanceFlag; Flags] +InstanceFlags_Default :: InstanceFlags{} + +Dx12Compiler :: enum i32 { + Undefined, + Fxc, + Dxc, +} + +Gles3MinorVersion :: enum i32 { + Automatic, + Version0, + Version1, + Version2, +} + +PipelineStatisticName :: enum i32 { + VertexShaderInvocations, + ClipperInvocations, + ClipperPrimitivesOut, + FragmentShaderInvocations, + ComputeShaderInvocations, +} + +InstanceExtras :: struct { + using chain: ChainedStruct, + backends: InstanceBackendFlags, + flags: InstanceFlags, + dx12ShaderCompiler: Dx12Compiler, + gles3MinorVersion: Gles3MinorVersion, + dxilPath: cstring, + dxcPath: cstring, +} + +DeviceExtras :: struct { + using chain: ChainedStruct, + tracePath: cstring, +} + +NativeLimits :: struct { + maxPushConstantSize: u32, + maxNonSamplerBindings: u32, +} + +RequiredLimitsExtras :: struct { + using chain: ChainedStruct, + limits: NativeLimits, +} + +SupportedLimitsExtras :: struct { + using chain: ChainedStructOut, + limits: NativeLimits, +} + +PushConstantRange :: struct { + stages: ShaderStageFlags, + start: u32, + end: u32, +} + +PipelineLayoutExtras :: struct { + using chain: ChainedStruct, + pushConstantRangeCount: uint, + pushConstantRanges: [^]PushConstantRange `fmt:"v,pushConstantRangeCount"`, +} + +SubmissionIndex :: distinct u64 + +WrappedSubmissionIndex :: struct { + queue: Queue, + submissionIndex: SubmissionIndex, +} + +ShaderDefine :: struct { + name: cstring, + value: cstring, +} + +ShaderModuleGLSLDescriptor :: struct { + using chain: ChainedStruct, + stage: ShaderStage, + code: cstring, + defineCount: uint, + defines: [^]ShaderDefine `fmt:"v,defineCount"`, +} + +RegistryReport :: struct { + numAllocated: uint, + numKeptFromUser: uint, + numReleasedFromUser: uint, + numErrors: uint, + elementSize: uint, +} + +HubReport :: struct { + adapters: RegistryReport, + devices: RegistryReport, + queues: RegistryReport, + pipelineLayouts: RegistryReport, + shaderModules: RegistryReport, + bindGroupLayouts: RegistryReport, + bindGroups: RegistryReport, + commandBuffers: RegistryReport, + renderBundles: RegistryReport, + renderPipelines: RegistryReport, + computePipelines: RegistryReport, + querySets: RegistryReport, + buffers: RegistryReport, + textures: RegistryReport, + textureViews: RegistryReport, + samplers: RegistryReport, +} + +GlobalReport :: struct { + surfaces: RegistryReport, + backendType: BackendType, + vulkan: HubReport, + metal: HubReport, + dx12: HubReport, + gl: HubReport, +} + +InstanceEnumerateAdapterOptions :: struct { + nextInChain: ^ChainedStruct, + backends: InstanceBackendFlags, +} + +BindGroupEntryExtras :: struct { + using chain: ChainedStruct, + buffers: [^]Buffer `fmt:"v,bufferCount"`, + bufferCount: uint, + samplers: [^]Sampler `fmt:"v,samplerCount"`, + samplerCount: uint, + textureViews: [^]TextureView `fmt:"v,textureViewCount"`, + textureViewCount: uint, +} + +BindGroupLayoutEntryExtras :: struct { + using chain: ChainedStruct, + count: u32, +} + +QuerySetDescriptorExtras :: struct { + using chain: ChainedStruct, + pipelineStatistics: [^]PipelineStatisticName `fmt:"v,pipelineStatisticCount"`, + pipelineStatisticCount: uint, +} + +SurfaceConfigurationExtras :: struct { + using chain: ChainedStruct, + desiredMaximumFrameLatency: i32, +} + +LogCallback :: #type proc "c" (level: LogLevel, message: cstring, userdata: rawptr) + +// Wrappers + +ConvertOdinToWGPULogLevel :: proc(level: runtime.Logger_Level) -> LogLevel { + switch { + case level < .Debug: return .Trace + case level < .Info: return .Debug + case level < .Warning: return .Info + case level < .Error: return .Warn + case: return .Error + } +} + +ConvertWGPUToOdinLogLevel :: proc(level: LogLevel) -> runtime.Logger_Level { + switch level { + case .Off, .Trace, .Debug: return .Debug + case .Info: return .Info + case .Warn: return .Warning + case .Error: return .Error + case: return .Error + } +} + +ConvertLogLevel :: proc { + ConvertOdinToWGPULogLevel, + ConvertWGPUToOdinLogLevel, +} diff --git a/vendor/x11/xlib/xlib_const.odin b/vendor/x11/xlib/xlib_const.odin index 910940dec..ce31a4e76 100644 --- a/vendor/x11/xlib/xlib_const.odin +++ b/vendor/x11/xlib/xlib_const.odin @@ -1,6 +1,80 @@ //+build linux, freebsd, openbsd package xlib +/* ---- X11/extensions/XKB.h ---------------------------------------------------------*/ + +XkbMinLegalKeyCode :: 8 +XkbMaxLegalKeyCode :: 255 +XkbMaxKeyCount :: XkbMaxLegalKeyCode - XkbMinLegalKeyCode + 1 +XkbPerKeyBitArraySize :: (XkbMaxLegalKeyCode + 1) / 8 +XkbKeyNameLength :: 4 +XkbNumVirtualMods :: 16 +XkbNumIndicators :: 32 +XkbNumKbdGroups :: 4 +XkbAnyActionDataSize :: 7 +XkbUseCoreKbd :: 0x0100 +XkbActionMessageLength :: 6 + +XkbInfoMask :: bit_set[XkbInfoMaskBits; int] +XkbInfoMaskBits :: enum u32 { + KeyTypes = 0, + KeySyms = 1, + ModifierMap = 2, + ExplicitComponents = 3, + KeyActions = 4, + KeyBehaviors = 5, + VirtualMods = 6, + VirtualModMap = 7, +} + +XkbAllClientInfoMask :: XkbInfoMask { + .KeyTypes, + .KeySyms, + .ModifierMap, +} + +XkbAllServerInfoMask :: XkbInfoMask { + .ExplicitComponents, + .KeyActions, + .KeyBehaviors, + .VirtualMods, + .VirtualModMap, +} + +XkbEventMask :: bit_set[XkbEventType; int] +XkbEventType :: enum i32 { + NewKeyboardNotify = 0, + MapNotify = 1, + StateNotify = 2, + ControlsNotify = 3, + IndicatorStateNotify = 4, + IndicatorMapNotify = 5, + NamesNotify = 6, + CompatMapNotify = 7, + BellNotify = 8, + ActionMessage = 9, + AccessXNotify = 10, + ExtensionDeviceNotify = 11, +} + +XkbAllEventsMask :: XkbEventMask { + .NewKeyboardNotify, + .MapNotify, + .StateNotify, + .ControlsNotify, + .IndicatorStateNotify, + .IndicatorMapNotify, + .NamesNotify, + .CompatMapNotify, + .BellNotify, + .ActionMessage, + .AccessXNotify, + .ExtensionDeviceNotify, +} + + +/* ---- X11/Xlib.h ---------------------------------------------------------*/ + // Special values for many types. Most of these constants // aren't attached to a specific type. @@ -17,6 +91,11 @@ AllTemporary :: 0 CurrentTime :: 0 NoSymbol :: 0 +PropModeReplace :: 0 +PropModePrepend :: 1 +PropModeAppend :: 2 + +XA_ATOM :: Atom(4) XA_WM_CLASS :: Atom(67) XA_WM_CLIENT_MACHINE :: Atom(36) XA_WM_COMMAND :: Atom(34) diff --git a/vendor/x11/xlib/xlib_procs.odin b/vendor/x11/xlib/xlib_procs.odin index 5e999519b..17d172172 100644 --- a/vendor/x11/xlib/xlib_procs.odin +++ b/vendor/x11/xlib/xlib_procs.odin @@ -6,110 +6,117 @@ foreign xlib { @(link_name="_Xdebug") _Xdebug: i32 } +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) --- +} + /* ---- X11/Xlib.h ---------------------------------------------------------*/ -@(default_calling_convention="c") +@(default_calling_convention="c", link_prefix="X") foreign xlib { // Free data allocated by Xlib - XFree :: proc(ptr: rawptr) --- + Free :: proc(ptr: rawptr) --- // Opening/closing a display - XOpenDisplay :: proc(name: cstring) -> ^Display --- - XCloseDisplay :: proc(display: ^Display) --- - XSetCloseDownMode :: proc(display: ^Display, mode: CloseMode) --- + OpenDisplay :: proc(name: cstring) -> ^Display --- + CloseDisplay :: proc(display: ^Display) --- + SetCloseDownMode :: proc(display: ^Display, mode: CloseMode) --- // Generate a no-op request - XNoOp :: proc(display: ^Display) --- + NoOp :: proc(display: ^Display) --- // Display macros (connection) - XConnectionNumber :: proc(display: ^Display) -> i32 --- - XExtendedMaxRequestSize :: - proc(display: ^Display) -> int --- - XMaxRequestSize :: proc(display: ^Display) -> int --- - XLastKnownRequestProcessed :: - proc(display: ^Display) -> uint --- - XNextRequest :: proc(display: ^Display) -> uint --- - XProtocolVersion :: proc(display: ^Display) -> i32 --- - XProtocolRevision :: proc(display: ^Display) -> i32 --- - XQLength :: proc(display: ^Display) -> i32 --- - XServerVendor :: proc(display: ^Display) -> cstring --- - XVendorRelease :: proc(display: ^Display) -> i32 --- + ConnectionNumber :: proc(display: ^Display) -> i32 --- + ExtendedMaxRequestSize :: proc(display: ^Display) -> int --- + MaxRequestSize :: proc(display: ^Display) -> int --- + LastKnownRequestProcessed :: proc(display: ^Display) -> uint --- + NextRequest :: proc(display: ^Display) -> uint --- + ProtocolVersion :: proc(display: ^Display) -> i32 --- + ProtocolRevision :: proc(display: ^Display) -> i32 --- + QLength :: proc(display: ^Display) -> i32 --- + ServerVendor :: proc(display: ^Display) -> cstring --- + VendorRelease :: proc(display: ^Display) -> i32 --- // Display macros (display properties) - XBlackPixel :: proc(display: ^Display, screen_no: i32) -> uint --- - XWhitePixel :: proc(display: ^Display, screen_no: i32) -> uint --- - XListDepths :: proc(display: ^Display, screen_no: i32, count: ^i32) -> [^]i32 --- - XDisplayCells :: proc(display: ^Display, screen_no: i32) -> i32 --- - XDisplayPlanes :: proc(display: ^Display, screen_no: i32) -> i32 --- - XScreenOfDisplay :: proc(display: ^Display, screen_no: i32) -> ^Screen --- - XDisplayString :: proc(display: ^Display) -> cstring --- + BlackPixel :: proc(display: ^Display, screen_no: i32) -> uint --- + WhitePixel :: proc(display: ^Display, screen_no: i32) -> uint --- + ListDepths :: proc(display: ^Display, screen_no: i32, count: ^i32) -> [^]i32 --- + DisplayCells :: proc(display: ^Display, screen_no: i32) -> i32 --- + DisplayPlanes :: proc(display: ^Display, screen_no: i32) -> i32 --- + ScreenOfDisplay :: proc(display: ^Display, screen_no: i32) -> ^Screen --- + DisplayString :: proc(display: ^Display) -> cstring --- // Display macros (defaults) - XDefaultColormap :: proc(display: ^Display, screen_no: i32) -> Colormap --- - XDefaultDepth :: proc(display: ^Display) -> i32 --- - XDefaultGC :: proc(display: ^Display, screen_no: i32) -> GC --- - XDefaultRootWindow :: proc(display: ^Display) -> Window --- - XDefaultScreen :: proc(display: ^Display) -> i32 --- - XDefaultVisual :: proc(display: ^Display, screen_no: i32) -> ^Visual --- - XDefaultScreenOfDisplay :: - proc(display: ^Display) -> ^Screen --- + DefaultColormap :: proc(display: ^Display, screen_no: i32) -> Colormap --- + DefaultDepth :: proc(display: ^Display) -> i32 --- + DefaultGC :: proc(display: ^Display, screen_no: i32) -> GC --- + DefaultRootWindow :: proc(display: ^Display) -> Window --- + DefaultScreen :: proc(display: ^Display) -> i32 --- + DefaultVisual :: proc(display: ^Display, screen_no: i32) -> ^Visual --- + DefaultScreenOfDisplay :: proc(display: ^Display) -> ^Screen --- // Display macros (other) - XRootWindow :: proc(display: ^Display, screen_no: i32) -> Window --- - XScreenCount :: proc(display: ^Display) -> i32 --- + RootWindow :: proc(display: ^Display, screen_no: i32) -> Window --- + ScreenCount :: proc(display: ^Display) -> i32 --- // Display image format macros - XListPixmapFormats :: proc(display: ^Display, count: ^i32) -> [^]XPixmapFormatValues --- - XImageByteOrder :: proc(display: ^Display) -> ByteOrder --- - XBitmapUnit :: proc(display: ^Display) -> i32 --- - XBitmapBitOrder :: proc(display: ^Display) -> ByteOrder --- - XBitmapPad :: proc(display: ^Display) -> i32 --- - XDisplayHeight :: proc(display: ^Display, screen_no: i32) -> i32 --- - XDisplayHeightMM :: proc(display: ^Display, screen_no: i32) -> i32 --- - XDisplayWidth :: proc(display: ^Display, screen_no: i32) -> i32 --- - XDisplayWidthMM :: proc(display: ^Display, screen_no: i32) -> i32 --- + ListPixmapFormats :: proc(display: ^Display, count: ^i32) -> [^]XPixmapFormatValues --- + ImageByteOrder :: proc(display: ^Display) -> ByteOrder --- + BitmapUnit :: proc(display: ^Display) -> i32 --- + BitmapBitOrder :: proc(display: ^Display) -> ByteOrder --- + BitmapPad :: proc(display: ^Display) -> i32 --- + DisplayHeight :: proc(display: ^Display, screen_no: i32) -> i32 --- + DisplayHeightMM :: proc(display: ^Display, screen_no: i32) -> i32 --- + DisplayWidth :: proc(display: ^Display, screen_no: i32) -> i32 --- + DisplayWidthMM :: proc(display: ^Display, screen_no: i32) -> i32 --- // Screen macros - XBlackPixelsOfScreen :: proc(screen: ^Screen) -> uint --- - XWhitePixelsOfScreen :: proc(screen: ^Screen) -> uint --- - XCellsOfScreen :: proc(screen: ^Screen) -> i32 --- - XDefaultColormapOfScreen :: proc(screen: ^Screen) -> Colormap --- - XDefaultDepthOfScreen :: proc(screen: ^Screen) -> i32 --- - XDefaultGCOfScreen :: proc(screen: ^Screen) -> GC --- - XDefaultVisualOfScreen :: proc(screen: ^Screen) -> ^Visual --- - XDoesBackingStore :: proc(screen: ^Screen) -> BackingStore --- - XDoesSaveUnders :: proc(screen: ^Screen) -> b32 --- - XDisplayOfScreen :: proc(screen: ^Screen) -> ^Display --- - XScreenNumberOfScreens :: proc(screen: ^Screen) -> i32 --- - XEventMaskOfScreen :: proc(screen: ^Screen) -> EventMask --- - XWidthOfScreen :: proc(screen: ^Screen) -> i32 --- - XHeightOfScreen :: proc(screen: ^Screen) -> i32 --- - XWidthMMOfScreen :: proc(screen: ^Screen) -> i32 --- - XHeightMMOfScreen :: proc(screen: ^Screen) -> i32 --- - XMaxCmapsOfScreen :: proc(screen: ^Screen) -> i32 --- - XMinCmapsOfScreen :: proc(screen: ^Screen) -> i32 --- - XPlanesOfScreen :: proc(screen: ^Screen) -> i32 --- - XRootWindowOfScreen :: proc(screen: ^Screen) -> Window --- + BlackPixelsOfScreen :: proc(screen: ^Screen) -> uint --- + WhitePixelsOfScreen :: proc(screen: ^Screen) -> uint --- + CellsOfScreen :: proc(screen: ^Screen) -> i32 --- + DefaultColormapOfScreen :: proc(screen: ^Screen) -> Colormap --- + DefaultDepthOfScreen :: proc(screen: ^Screen) -> i32 --- + DefaultGCOfScreen :: proc(screen: ^Screen) -> GC --- + DefaultVisualOfScreen :: proc(screen: ^Screen) -> ^Visual --- + DoesBackingStore :: proc(screen: ^Screen) -> BackingStore --- + DoesSaveUnders :: proc(screen: ^Screen) -> b32 --- + DisplayOfScreen :: proc(screen: ^Screen) -> ^Display --- + ScreenNumberOfScreen :: proc(screen: ^Screen) -> i32 --- + EventMaskOfScreen :: proc(screen: ^Screen) -> EventMask --- + WidthOfScreen :: proc(screen: ^Screen) -> i32 --- + HeightOfScreen :: proc(screen: ^Screen) -> i32 --- + WidthMMOfScreen :: proc(screen: ^Screen) -> i32 --- + HeightMMOfScreen :: proc(screen: ^Screen) -> i32 --- + MaxCmapsOfScreen :: proc(screen: ^Screen) -> i32 --- + MinCmapsOfScreen :: proc(screen: ^Screen) -> i32 --- + PlanesOfScreen :: proc(screen: ^Screen) -> i32 --- + RootWindowOfScreen :: proc(screen: ^Screen) -> Window --- // Threading functions - XInitThreads :: proc() -> Status --- - XLockDisplay :: proc(display: ^Display) --- - XUnlockDisplay :: proc(display: ^Display) --- + InitThreads :: proc() -> Status --- + LockDisplay :: proc(display: ^Display) --- + UnlockDisplay :: proc(display: ^Display) --- // Internal connections - XAddConnectionWatch :: proc( + AddConnectionWatch :: proc( display: ^Display, procedure: XConnectionWatchProc, data: rawptr, ) -> Status --- - XRemoveConnectionWatch :: proc( + RemoveConnectionWatch :: proc( display: ^Display, procedure: XConnectionWatchProc, data: rawptr, ) -> Status --- - XProcessInternalConnections :: proc( + ProcessInternalConnections :: proc( display: ^Display, fd: i32, ) --- - XInternalConnectionNumbers :: proc( + InternalConnectionNumbers :: proc( display: ^Display, fds: ^[^]i32, count: ^i32, ) -> Status --- // Windows functions - XVisualIDFromVisual :: proc(visual: ^Visual) -> VisualID --- + VisualIDFromVisual :: proc(visual: ^Visual) -> VisualID --- // Windows: creation/destruction - XCreateWindow :: proc( + CreateWindow :: proc( display: ^Display, parent: Window, x: i32, @@ -123,7 +130,7 @@ foreign xlib { attr_mask: WindowAttributeMask, attr: ^XSetWindowAttributes, ) -> Window --- - XCreateSimpleWindow :: proc( + CreateSimpleWindow :: proc( display: ^Display, parent: Window, x: i32, @@ -134,34 +141,34 @@ foreign xlib { border: int, bg: int, ) -> Window --- - XDestroyWindow :: proc(display: ^Display, window: Window) --- - XDestroySubwindows :: proc(display: ^Display, window: Window) --- + DestroyWindow :: proc(display: ^Display, window: Window) --- + DestroySubwindows :: proc(display: ^Display, window: Window) --- // Windows: mapping/unmapping - XMapWindow :: proc(display: ^Display, window: Window) --- - XMapRaised :: proc(display: ^Display, window: Window) --- - XMapSubwindows :: proc(display: ^Display, window: Window) --- - XUnmapWindow :: proc(display: ^Display, window: Window) --- - XUnmapSubwindows :: proc(display: ^Display, window: Window) --- + MapWindow :: proc(display: ^Display, window: Window) --- + MapRaised :: proc(display: ^Display, window: Window) --- + MapSubwindows :: proc(display: ^Display, window: Window) --- + UnmapWindow :: proc(display: ^Display, window: Window) --- + UnmapSubwindows :: proc(display: ^Display, window: Window) --- // Windows: configuring - XConfigureWindow :: proc( + ConfigureWindow :: proc( display: ^Display, window: Window, mask: WindowChangesMask, values: XWindowChanges, ) --- - XMoveWindow :: proc( + MoveWindow :: proc( display: ^Display, window: Window, x: i32, y: i32, ) --- - XResizeWindow :: proc( + ResizeWindow :: proc( display: ^Display, window: Window, width: u32, height: u32, ) --- - XMoveResizeWindow :: proc( + MoveResizeWindow :: proc( display: ^Display, window: Window, x: i32, @@ -169,51 +176,51 @@ foreign xlib { width: u32, height: u32, ) --- - XSetWindowBorderWidth :: proc( + SetWindowBorderWidth :: proc( display: ^Display, window: Window, width: u32, ) --- // Window: changing stacking order - XRaiseWindow :: proc(display: ^Display, window: Window) --- - XLowerWindow :: proc(display: ^Display, window: Window) --- - XCirculateSubwindows :: proc(display: ^Display, window: Window, direction: CirculationDirection) --- - XCirculateSubwindowsUp :: proc(display: ^Display, window: Window) --- - XCirculateSubwindowsDown :: proc(display: ^Display, window: Window) --- - XRestackWindows :: proc(display: ^Display, windows: [^]Window, nwindows: i32) --- + RaiseWindow :: proc(display: ^Display, window: Window) --- + LowerWindow :: proc(display: ^Display, window: Window) --- + CirculateSubwindows :: proc(display: ^Display, window: Window, direction: CirculationDirection) --- + CirculateSubwindowsUp :: proc(display: ^Display, window: Window) --- + CirculateSubwindowsDown :: proc(display: ^Display, window: Window) --- + RestackWindows :: proc(display: ^Display, windows: [^]Window, nwindows: i32) --- // Window: changing attributes - XChangeWindowAttributes :: proc( + ChangeWindowAttributes :: proc( display: ^Display, window: Window, attr_mask: WindowAttributeMask, attr: XWindowAttributes, ) --- - XSetWindowBackground :: proc( + SetWindowBackground :: proc( display: ^Display, window: Window, pixel: uint, ) --- - XSetWindowBackgroundMap :: proc( + SetWindowBackgroundMap :: proc( display: ^Display, window: Window, pixmap: Pixmap, ) --- - XSetWindowColormap :: proc( + SetWindowColormap :: proc( display: ^Display, window: Window, colormap: Colormap, ) --- - XDefineCursor :: proc( + DefineCursor :: proc( display: ^Display, window: Window, cursor: Cursor, ) --- - XUndefineCursor :: proc( + UndefineCursor :: proc( display: ^Display, window: Window, ) --- // Windows: querying information - XQueryTree :: proc( + QueryTree :: proc( display: ^Display, window: Window, root: ^Window, @@ -221,12 +228,12 @@ foreign xlib { children: ^[^]Window, nchildren: ^u32, ) -> Status --- - XGetWindowAttributes :: proc( + GetWindowAttributes :: proc( display: ^Display, window: Window, attr: ^XWindowAttributes, ) --- - XGetGeometry :: proc( + GetGeometry :: proc( display: ^Display, drawable: Drawable, root: ^Window, @@ -238,7 +245,7 @@ foreign xlib { depth: ^u32, ) -> Status --- // Windows: translating screen coordinates - XTranslateCoordinates :: proc( + TranslateCoordinates :: proc( display: ^Display, src_window: Window, dst_window: Window, @@ -247,7 +254,7 @@ foreign xlib { dst_x: ^i32, dst_y: ^i32, ) -> b32 --- - XQueryPointer :: proc( + QueryPointer :: proc( display: ^Display, window: Window, root: ^Window, @@ -259,29 +266,29 @@ foreign xlib { mask: ^KeyMask, ) -> b32 --- // Atoms - XInternAtom :: proc( + InternAtom :: proc( display: ^Display, name: cstring, existing: b32, ) -> Atom --- - XInternAtoms :: proc( + InternAtoms :: proc( display: ^Display, names: [^]cstring, count: i32, atoms: [^]Atom, ) -> Status --- - XGetAtomName :: proc( + GetAtomName :: proc( display: ^Display, atom: Atom, ) -> cstring --- - XGetAtomNames :: proc( + GetAtomNames :: proc( display: ^Display, atoms: [^]Atom, count: i32, names: [^]cstring, ) -> Status --- // Windows: Obtaining and changing properties - XGetWindowProperty :: proc( + GetWindowProperty :: proc( display: ^Display, window: Window, property: Atom, @@ -295,12 +302,12 @@ foreign xlib { bytes_after: [^]uint, props: ^rawptr, ) -> i32 --- - XListProperties :: proc( + ListProperties :: proc( display: ^Display, window: Window, num: ^i32, ) -> [^]Atom --- - XChangeProperty :: proc( + ChangeProperty :: proc( display: ^Display, window: Window, property: Atom, @@ -310,30 +317,30 @@ foreign xlib { data: rawptr, count: i32, ) --- - XRotateWindowProperties :: proc( + RotateWindowProperties :: proc( display: ^Display, window: Window, props: [^]Atom, nprops: i32, npos: i32, ) --- - XDeleteProperty :: proc( + DeleteProperty :: proc( display: ^Display, window: Window, prop: Atom, ) --- // Selections - XSetSelectionOwner :: proc( + SetSelectionOwner :: proc( display: ^Display, selection: Atom, owber: Window, time: Time, ) --- - XGetSelectionOwner :: proc( + GetSelectionOwner :: proc( display: ^Display, selection: Atom, ) -> Window --- - XConvertSelection :: proc( + ConvertSelection :: proc( display: ^Display, selection: Atom, target: Atom, @@ -342,23 +349,23 @@ foreign xlib { time: Time, ) --- // Creating and freeing pixmaps - XCreatePixmap :: proc( + CreatePixmap :: proc( display: ^Display, drawable: Drawable, width: u32, height: u32, depth: u32, ) -> Pixmap --- - XFreePixmap :: proc( + FreePixmap :: proc( display: ^Display, pixmap: Pixmap, ) --- // Creating recoloring and freeing cursors - XCreateFontCursor :: proc( + CreateFontCursor :: proc( display: ^Display, shape: CursorShape, ) -> Cursor --- - XCreateGlyphCursor :: proc( + CreateGlyphCursor :: proc( display: ^Display, src_font: Font, mask_font: Font, @@ -367,7 +374,7 @@ foreign xlib { fg: ^XColor, bg: ^XColor, ) -> Cursor --- - XCreatePixmapCursor :: proc( + CreatePixmapCursor :: proc( display: ^Display, source: Pixmap, mask: Pixmap, @@ -376,7 +383,7 @@ foreign xlib { x: u32, y: u32, ) -> Cursor --- - XQueryBestCursor :: proc( + QueryBestCursor :: proc( display: ^Display, drawable: Drawable, width: u32, @@ -384,72 +391,50 @@ foreign xlib { out_width: ^u32, out_height: ^u32, ) -> Status --- - XRecolorCursor :: proc( + RecolorCursor :: proc( display: ^Display, cursor: Cursor, fg: ^XColor, bg: ^XColor, ) --- - XFreeCursor :: proc(display: ^Display, cursor: Cursor) --- + FreeCursor :: proc(display: ^Display, cursor: Cursor) --- // Creation/destruction of colormaps - XCreateColormap :: proc( + CreateColormap :: proc( display: ^Display, window: Window, visual: ^Visual, alloc: ColormapAlloc, ) -> Colormap --- - XCopyColormapAndFree :: proc( + CopyColormapAndFree :: proc( display: ^Display, colormap: Colormap, ) -> Colormap --- - XFreeColormap :: proc( + FreeColormap :: proc( display: ^Display, colormap: Colormap, ) --- // Mapping color names to values - XLookupColor :: proc( + LookupColor :: proc( display: ^Display, colomap: Colormap, name: cstring, exact: ^XColor, screen: ^XColor, ) -> Status --- - XcmsLookupColor :: proc( - display: ^Display, - colormap: Colormap, - name: cstring, - exact: XcmsColor, - screen: XcmsColor, - format: XcmsColorFormat, - ) -> Status --- // Allocating and freeing color cells - XAllocColor :: proc( + AllocColor :: proc( display: ^Display, colormap: Colormap, screen: ^XColor, ) -> Status --- - XcmsAllocColor :: proc( - display: ^Display, - colormap: Colormap, - color: ^XcmsColor, - format: XcmsColorFormat, - ) -> Status --- - XAllocNamedColor :: proc( + AllocNamedColor :: proc( display: ^Display, colormap: Colormap, name: cstring, screen: ^XColor, exact: ^XColor, ) -> Status --- - XcmsAllocNamedColor :: proc( - display: ^Display, - colormap: Colormap, - name: cstring, - screen: ^XcmsColor, - exact: ^XcmsColor, - format: XcmsColorFormat, - ) -> Status --- - XAllocColorCells :: proc( + AllocColorCells :: proc( display: ^Display, colormap: Colormap, contig: b32, @@ -458,7 +443,7 @@ foreign xlib { pixels: [^]uint, npixels: u32, ) -> Status --- - XAllocColorPlanes :: proc( + AllocColorPlanes :: proc( display: ^Display, colormap: Colormap, contig: b32, @@ -471,7 +456,7 @@ foreign xlib { gmask: [^]uint, bmask: [^]uint, ) -> Status --- - XFreeColors :: proc( + FreeColors :: proc( display: ^Display, colormap: Colormap, pixels: [^]uint, @@ -479,17 +464,1198 @@ foreign xlib { planes: uint, ) --- // Modifying and querying colormap cells - XStoreColor :: proc( + StoreColor :: proc( display: ^Display, colormap: Colormap, color: ^XColor, ) --- - XStoreColors :: proc( + StoreColors :: proc( display: ^Display, colormap: Colormap, color: [^]XColor, ncolors: i32, ) --- + // Graphics context functions + CreateGC :: proc( + display: ^Display, + drawable: Drawable, + mask: GCAttributeMask, + attr: ^XGCValues, + ) -> GC --- + CopyGC :: proc( + display: ^Display, + src: GC, + dst: GC, + mask: GCAttributeMask, + ) --- + ChangeGC :: proc( + display: ^Display, + gc: GC, + mask: GCAttributeMask, + values: ^XGCValues, + ) --- + GetGCValues :: proc( + display: ^Display, + gc: GC, + mask: GCAttributeMask, + values: ^XGCValues, + ) -> Status --- + FreeGC :: proc(display: ^Display, gc: GC) --- + GCContextFromGC :: proc(gc: GC) -> GContext --- + FlushGC :: proc(display: ^Display, gc: GC) --- + // Convenience routines for GC + SetState :: proc( + display: ^Display, + gc: GC, + fg: uint, + bg: uint, + fn: GCFunction, + pmask: uint, + ) --- + SetForeground :: proc( + display: ^Display, + gc: GC, + fg: uint, + ) --- + SetBackground :: proc( + display: ^Display, + gc: GC, + bg: uint, + ) --- + SetFunction :: proc( + display: ^Display, + gc: GC, + fn: GCFunction, + ) --- + SetPlaneMask :: proc( + display: ^Display, + gc: GC, + pmask: uint, + ) --- + SetLineAttributes :: proc( + display: ^Display, + gc: GC, + width: u32, + line_style: LineStyle, + cap_style: CapStyle, + join_style: JoinStyle, + ) --- + SetDashes :: proc( + display: ^Display, + gc: GC, + dash_offs: i32, + dash_list: [^]i8, + n: i32, + ) --- + SetFillStyle :: proc( + display: ^Display, + gc: GC, + style: FillStyle, + ) --- + SetFillRule :: proc( + display: ^Display, + gc: GC, + rule: FillRule, + ) --- + QueryBestSize :: proc( + display: ^Display, + class: i32, + which: Drawable, + width: u32, + height: u32, + out_width: ^u32, + out_height: ^u32, + ) -> Status --- + QueryBestTile :: proc( + display: ^Display, + which: Drawable, + width: u32, + height: u32, + out_width: ^u32, + out_height: ^u32, + ) -> Status --- + QueryBestStripple :: proc( + display: ^Display, + which: Drawable, + width: u32, + height: u32, + out_width: u32, + out_height: u32, + ) -> Status --- + SetTile :: proc(display: ^Display, gc: GC, tile: Pixmap) --- + SetStripple :: proc(display: ^Display, gc: GC, stripple: Pixmap) --- + SetTSOrigin :: proc(display: ^Display, gc: GC, x: i32, y: i32) --- + SetFont :: proc(display: ^Display, gc: GC, font: Font) --- + SetClipOrigin :: proc(display: ^Display, gc: GC, x: i32, y: i32) --- + SetClipMask :: proc(display: ^Display, gc: GC, pixmap: Pixmap) --- + SetClipRectangles :: proc( + display: ^Display, + gc: GC, + x: i32, + y: i32, + rects: [^]XRectangle, + n: i32, + ordering: i32, + ) --- + SetArcMode :: proc(display: ^Display, gc: GC, mode: ArcMode) --- + SetSubwindowMode :: proc(display: ^Display, gc: GC, mode: SubwindowMode) --- + SetGraphicsExposures :: proc(display: ^Display, gc: GC, exp: b32) --- + // Graphics functions + ClearArea :: proc( + display: ^Display, + window: Window, + x: i32, + y: i32, + width: u32, + height: u32, + exp: b32, + ) --- + ClearWindow :: proc( + display: ^Display, + window: Window, + ) --- + CopyArea :: proc( + display: ^Display, + src: Drawable, + dst: Drawable, + gc: GC, + src_x: i32, + src_y: i32, + width: u32, + height: u32, + dst_x: i32, + dst_y: i32, + ) --- + CopyPlane :: proc( + display: ^Display, + src: Drawable, + dst: Drawable, + gc: GC, + src_x: i32, + src_y: i32, + width: u32, + height: u32, + dst_x: i32, + dst_y: i32, + plane: uint, + ) --- + // Drawing lines, points, rectangles and arc + DrawPoint :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + ) --- + DrawPoints :: proc( + display: Display, + drawable: Drawable, + gc: GC, + point: [^]XPoint, + npoints: i32, + mode: CoordMode, + ) --- + DrawLine :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x1: i32, + y1: i32, + x2: i32, + y2: i32, + ) --- + DrawLines :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + points: [^]XPoint, + npoints: i32, + ) --- + DrawSegments :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + segs: [^]XSegment, + nsegs: i32, + ) --- + DrawRectangle :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + width: u32, + height: u32, + ) --- + DrawRectangles :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + rects: [^]XRectangle, + nrects: i32, + ) --- + DrawArc :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + width: u32, + height: u32, + angle1: i32, + angle2: i32, + ) --- + DrawArcs :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + arcs: [^]XArc, + narcs: i32, + ) --- + // Filling areas + FillRectangle :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + width: u32, + height: u32, + ) --- + FillRectangles :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + rects: [^]XRectangle, + nrects: i32, + ) --- + FillPolygon :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + points: [^]XPoint, + npoints: i32, + shape: Shape, + mode: CoordMode, + ) --- + FillArc :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + width: u32, + height: u32, + angle1: i32, + angle2: i32, + ) --- + FillArcs :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + arcs: [^]XArc, + narcs: i32, + ) --- + // Font metrics + LoadFont :: proc(display: ^Display, name: cstring) -> Font --- + QueryFont :: proc(display: ^Display, id: XID) -> ^XFontStruct --- + LoadQueryFont :: proc(display: ^Display, name: cstring) -> ^XFontStruct --- + FreeFont :: proc(display: ^Display, font_struct: ^XFontStruct) --- + GetFontProperty :: proc(font_struct: ^XFontStruct, atom: Atom, ret: ^uint) -> b32 --- + UnloadFont :: proc(display: ^Display, font: Font) --- + ListFonts :: proc(display: ^Display, pat: cstring, max: i32, count: ^i32) -> [^]cstring --- + FreeFontNames :: proc(display: ^Display, list: [^]cstring) --- + ListFontsWithInfo :: proc( + display: ^Display, + pat: cstring, + max: i32, + count: ^i32, + info: ^[^]XFontStruct, + ) -> [^]cstring --- + FreeFontInfo :: proc(names: [^]cstring, info: [^]XFontStruct, count: i32) --- + // Computing character string sizes + TextWidth :: proc(font_struct: ^XFontStruct, string: [^]u8, count: i32) -> i32 --- + TextWidth16 :: proc(font_struct: ^XFontStruct, string: [^]XChar2b, count: i32) -> i32 --- + TextExtents :: proc( + font_struct: ^XFontStruct, + string: [^]u8, + nchars: i32, + direction: ^FontDirection, + ascent: ^i32, + descent: ^i32, + ret: ^XCharStruct, + ) --- + TextExtents16 :: proc( + font_struct: ^XFontStruct, + string: [^]XChar2b, + nchars: i32, + direction: ^FontDirection, + ascent: ^i32, + descent: ^i32, + ret: ^XCharStruct, + ) --- + QueryTextExtents :: proc( + display: ^Display, + font_id: XID, + string: [^]u8, + nchars: i32, + direction: ^FontDirection, + ascent: ^i32, + descent: ^i32, + ret: ^XCharStruct, + ) --- + QueryTextExtents16 :: proc( + display: ^Display, + font_id: XID, + string: [^]XChar2b, + nchars: i32, + direction: ^FontDirection, + ascent: ^i32, + descent: ^i32, + ret: ^XCharStruct, + ) --- + // Drawing complex text + DrawText :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + items: XTextItem, + nitems: i32, + ) --- + DrawText16 :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + items: XTextItem16, + nitems: i32, + ) --- + // Drawing text characters + DrawString :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + string: [^]u8, + length: i32, + ) --- + DrawString16 :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + string: [^]XChar2b, + length: i32, + ) --- + DrawImageString :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + string: [^]u8, + length: i32, + ) --- + DrawImageString16 :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + string: [^]XChar2b, + length: i32, + ) --- + // Transferring images between client and server + InitImage :: proc(image: ^XImage) -> Status --- + PutImage :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + image: ^XImage, + src_x: i32, + src_y: i32, + dst_x: i32, + dst_y: i32, + width: u32, + height: u32, + ) --- + GetImage :: proc( + display: ^Display, + drawable: Drawable, + x: i32, + y: i32, + width: u32, + height: u32, + mask: uint, + format: ImageFormat, + ) -> ^XImage --- + GetSubImage :: proc( + display: ^Display, + drawable: Drawable, + src_x: i32, + src_y: i32, + width: u32, + height: u32, + mask: uint, + format: ImageFormat, + dst: ^XImage, + dst_x: i32, + dst_y: i32, + ) -> ^XImage --- + // Window and session manager functions + ReparentWindow :: proc( + display: ^Display, + window: Window, + parent: Window, + x: i32, + y: i32, + ) --- + ChangeSaveSet :: proc( + display: ^Display, + window: Window, + mode: SaveSetChangeMode, + ) --- + AddToSaveSet :: proc( + display: ^Display, + window: Window, + ) --- + RemoveFromSaveSet :: proc( + display: ^Display, + window: Window, + ) --- + // Managing installed colormaps + InstallColormap :: proc(display: ^Display, colormap: Colormap) --- + UninstallColormap :: proc(display: ^Display, colormap: Colormap) --- + ListInstalledColormaps :: proc(display: ^Display, window: Window, n: ^i32) -> [^]Colormap --- + // Setting and retrieving font search paths + SetFontPath :: proc(display: ^Display, dirs: [^]cstring, ndirs: i32) --- + GetFontPath :: proc(display: ^Display, npaths: ^i32) -> [^]cstring --- + FreeFontPath :: proc(list: [^]cstring) --- + // Grabbing the server + GrabServer :: proc(display: ^Display) --- + UngrabServer :: proc(display: ^Display) --- + // Killing clients + KillClient :: proc(display: ^Display, resource: XID) --- + // Controlling the screen saver + SetScreenSaver :: proc( + display: ^Display, + timeout: i32, + interval: i32, + blanking: ScreenSaverBlanking, + exposures: ScreenSavingExposures, + ) --- + ForceScreenSaver :: proc(display: ^Display, mode: ScreenSaverForceMode) --- + ActivateScreenSaver :: proc(display: ^Display) --- + ResetScreenSaver :: proc(display: ^Display) --- + GetScreenSaver :: proc( + display: ^Display, + timeout: ^i32, + interval: ^i32, + blanking: ^ScreenSaverBlanking, + exposures: ^ScreenSavingExposures, + ) --- + // Controlling host address + AddHost :: proc(display: ^Display, addr: ^XHostAddress) --- + AddHosts :: proc(display: ^Display, hosts: [^]XHostAddress, nhosts: i32) --- + ListHosts :: proc(display: ^Display, nhosts: ^i32, state: [^]b32) -> [^]XHostAddress --- + RemoveHost :: proc(display: ^Display, host: XHostAddress) --- + RemoveHosts :: proc(display: ^Display, hosts: [^]XHostAddress, nhosts: i32) --- + // Access control list + SetAccessControl :: proc(display: ^Display, mode: AccessControlMode) --- + EnableAccessControl :: proc(display: ^Display) --- + DisableAccessControl :: proc(display: ^Display) --- + // Events + SelectInput :: proc(display: ^Display, window: Window, mask: EventMask) --- + Flush :: proc(display: ^Display) --- + Sync :: proc(display: ^Display) --- + EventsQueued :: proc(display: ^Display, mode: EventQueueMode) -> i32 --- + Pending :: proc(display: ^Display) -> i32 --- + NextEvent :: proc(display: ^Display, event: ^XEvent) --- + PeekEvent :: proc(display: ^Display, event: ^XEvent) --- + GetEventData :: proc(display: ^Display, cookie: ^XGenericEventCookie) -> b32 --- + FreeEventData :: proc(display: ^Display, cookie: ^XGenericEventCookie) --- + // Selecting events using a predicate procedure + IfEvent :: proc( + display: ^Display, + event: ^XEvent, + predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, + ctx: rawptr, + ) --- + CheckIfEvent :: proc( + display: ^Display, + event: ^XEvent, + predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, + arg: rawptr, + ) -> b32 --- + PeekIfEvent :: proc( + display: ^Display, + event: ^XEvent, + predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, + ctx: rawptr, + ) --- + // Selecting events using a window or event mask + WindowEvent :: proc( + display: ^Display, + window: Window, + mask: EventMask, + event: ^XEvent, + ) --- + CheckWindowEvent :: proc( + display: ^Display, + window: Window, + mask: EventMask, + event: ^XEvent, + ) -> b32 --- + MaskEvent :: proc( + display: ^Display, + mask: EventMask, + event: ^XEvent, + ) --- + CheckMaskEvent :: proc( + display: ^Display, + mask: EventMask, + event: ^XEvent, + ) -> b32 --- + CheckTypedEvent :: proc( + display: ^Display, + type: EventType, + event: ^XEvent, + ) -> b32 --- + CheckTypedWindowEvent :: proc( + display: ^Display, + window: Window, + type: EventType, + event: ^XEvent, + ) -> b32 --- + // Putting events back + PutBackEvent :: proc( + display: ^Display, + event: ^XEvent, + ) --- + // Sending events to other applications + SendEvent :: proc( + display: ^Display, + window: Window, + propagate: b32, + mask: EventMask, + event: ^XEvent, + ) -> Status --- + // Getting the history of pointer motion + DisplayMotionBufferSize :: proc(display: ^Display) -> uint --- + GetMotionEvents :: proc( + display: ^Display, + window: Window, + start: Time, + stop: Time, + nevents: ^i32, + ) -> [^]XTimeCoord --- + // Enabling or disabling synchronization + SetAfterFunction :: proc( + display: ^Display, + procedure: #type proc "c" (display: ^Display) -> i32, + ) -> i32 --- + Synchronize :: proc( + display: ^Display, + onoff: b32, + ) -> i32 --- + // Error handling + SetErrorHandler :: proc( + handler: #type proc "c" (display: ^Display, event: ^XErrorEvent) -> i32, + ) -> i32 --- + GetErrorText :: proc( + display: ^Display, + code: i32, + buffer: [^]u8, + size: i32, + ) --- + GetErrorDatabaseText :: proc( + display: ^Display, + name: cstring, + message: cstring, + default_string: cstring, + buffer: [^]u8, + size: i32, + ) --- + DisplayName :: proc(string: cstring) -> cstring --- + SetIOErrorHandler :: proc( + handler: #type proc "c" (display: ^Display) -> i32, + ) -> i32 --- + // Pointer grabbing + GrabPointer :: proc( + display: ^Display, + grab_window: Window, + owner_events: b32, + mask: EventMask, + pointer_mode: GrabMode, + keyboard_mode: GrabMode, + confine_to: Window, + cursor: Cursor, + time: Time, + ) -> i32 --- + UngrabPointer :: proc( + display: ^Display, + time: Time, + ) -> i32 --- + ChangeActivePointerGrab :: proc( + display: ^Display, + event_mask: EventMask, + cursor: Cursor, + time: Time, + ) --- + GrabButton :: proc( + display: ^Display, + button: u32, + modifiers: InputMask, + grab_window: Window, + owner_events: b32, + event_mask: EventMask, + pointer_mode: GrabMode, + keyboard_mode: GrabMode, + confine_to: Window, + cursor: Cursor, + ) --- + UngrabButton :: proc( + display: ^Display, + button: u32, + modifiers: InputMask, + grab_window: Window, + ) --- + GrabKeyboard :: proc( + display: ^Display, + grab_window: Window, + owner_events: b32, + pointer_mode: GrabMode, + keyboard_mode: GrabMode, + time: Time, + ) -> i32 --- + UngrabKeyboard :: proc( + display: ^Display, + time: Time, + ) --- + GrabKey :: proc( + display: ^Display, + keycode: i32, + modifiers: InputMask, + grab_window: Window, + owner_events: b32, + pointer_mode: GrabMode, + keyboard_mode: GrabMode, + ) --- + UngrabKey :: proc( + display: ^Display, + keycode: i32, + modifiers: InputMask, + grab_window: Window, + ) --- + // Resuming event processing + AllowEvents :: proc(display: ^Display, evend_mode: AllowEventsMode, time: Time) --- + // Moving the pointer + WarpPointer :: proc( + display: ^Display, + src_window: Window, + dst_window: Window, + src_x: i32, + src_y: i32, + src_width: u32, + src_height: u32, + dst_x: i32, + dst_y: i32, + ) --- + // Controlling input focus + SetInputFocus :: proc( + display: ^Display, + focus: Window, + revert_to: FocusRevert, + time: Time, + ) --- + GetInputFocus :: proc( + display: ^Display, + focus: ^Window, + revert_to: ^FocusRevert, + ) --- + // Manipulating the keyboard and pointer settings + ChangeKeyboardControl :: proc( + display: ^Display, + mask: KeyboardControlMask, + values: ^XKeyboardControl, + ) --- + GetKeyboardControl :: proc( + display: ^Display, + values: ^XKeyboardState, + ) --- + AutoRepeatOn :: proc(display: ^Display) --- + AutoRepeatOff :: proc(display: ^Display) --- + Bell :: proc(display: ^Display, percent: i32) --- + QueryKeymap :: proc(display: ^Display, keys: [^]u32) --- + SetPointerMapping :: proc(display: ^Display, map_should_not_be_a_keyword: [^]u8, nmap: i32) -> i32 --- + GetPointerMapping :: proc(display: ^Display, map_should_not_be_a_keyword: [^]u8, nmap: i32) -> i32 --- + ChangePointerControl :: proc( + display: ^Display, + do_accel: b32, + do_threshold: b32, + accel_numerator: i32, + accel_denominator: i32, + threshold: i32, + ) --- + GetPointerControl :: proc( + display: ^Display, + accel_numerator: ^i32, + accel_denominator: ^i32, + threshold: ^i32, + ) --- + // Manipulating the keyboard encoding + DisplayKeycodes :: proc( + display: ^Display, + min_keycodes: ^i32, + max_keycodes: ^i32, + ) --- + GetKeyboardMapping :: proc( + display: ^Display, + first: KeyCode, + count: i32, + keysyms_per: ^i32, + ) -> ^KeySym --- + ChangeKeyboardMapping :: proc( + display: ^Display, + first: KeyCode, + keysyms_per: i32, + keysyms: [^]KeySym, + num_codes: i32, + ) --- + NewModifiermap :: proc(max_keys_per_mode: i32) -> ^XModifierKeymap --- + InsertModifiermapEntry :: proc( + modmap: ^XModifierKeymap, + keycode_entry: KeyCode, + modifier: i32, + ) -> ^XModifierKeymap --- + DeleteModifiermapEntry :: proc( + modmap: ^XModifierKeymap, + keycode_entry: KeyCode, + modifier: i32, + ) -> ^XModifierKeymap --- + FreeModifiermap :: proc(modmap: ^XModifierKeymap) --- + SetModifierMapping :: proc(display: ^Display, modmap: ^XModifierKeymap) -> i32 --- + GetModifierMapping :: proc(display: ^Display) -> ^XModifierKeymap --- + // Manipulating top-level windows + IconifyWindow :: proc( + dipslay: ^Display, + window: Window, + screen_no: i32, + ) -> Status --- + WithdrawWindow :: proc( + dipslay: ^Display, + window: Window, + screen_no: i32, + ) -> Status --- + ReconfigureWMWindow :: proc( + dipslay: ^Display, + window: Window, + screen_no: i32, + mask: WindowChangesMask, + changes: ^XWindowChanges, + ) -> Status --- + // Getting and setting the WM_NAME property + SetWMName :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) --- + GetWMName :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) -> Status --- + StoreName :: proc( + display: ^Display, + window: Window, + name: cstring, + ) --- + FetchName :: proc( + display: ^Display, + window: Window, + name: ^cstring, + ) -> Status --- + SetWMIconName :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) --- + GetWMIconName :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) -> Status --- + SetIconName :: proc( + display: ^Display, + window: Window, + name: cstring, + ) --- + GetIconName :: proc( + display: ^Display, + window: Window, + prop: ^cstring, + ) -> Status --- + // Setting and reading WM_HINTS property + AllocWMHints :: proc() -> ^XWMHints --- + SetWMHints :: proc( + display: ^Display, + window: Window, + hints: ^XWMHints, + ) --- + GetWMHints :: proc( + display: ^Display, + window: Window, + ) -> ^XWMHints --- + // Setting and reading MW_NORMAL_HINTS property + AllocSizeHints :: proc() -> ^XSizeHints --- + SetWMNormalHints :: proc( + display: ^Display, + window: Window, + hints: ^XSizeHints, + ) --- + GetWMNormalHints :: proc( + display: ^Display, + window: Window, + hints: ^XSizeHints, + flags: ^SizeHints, + ) -> Status --- + SetWMSizeHints :: proc( + display: ^Display, + window: Window, + hints: ^XSizeHints, + prop: Atom, + ) --- + GetWMSizeHints :: proc( + display: ^Display, + window: Window, + hints: ^XSizeHints, + masks: ^SizeHints, + prop: Atom, + ) -> Status --- + // Setting and reading the WM_CLASS property + AllocClassHint :: proc() -> ^XClassHint --- + SetClassHint :: proc( + display: ^Display, + window: Window, + hint: ^XClassHint, + ) --- + GetClassHint :: proc( + display: ^Display, + window: Window, + hint: ^XClassHint, + ) -> Status --- + // Setting and reading WM_TRANSIENT_FOR property + SetTransientForHint :: proc( + display: ^Display, + window: Window, + prop_window: Window, + ) --- + GetTransientForHint :: proc( + display: ^Display, + window: Window, + prop_window: ^Window, + ) -> Status --- + // Setting and reading the WM_PROTOCOLS property + SetWMProtocols :: proc( + display: ^Display, + window: Window, + protocols: [^]Atom, + count: i32, + ) -> Status --- + GetWMProtocols :: proc( + display: ^Display, + window: Window, + protocols: ^[^]Atom, + count: ^i32, + ) -> Status --- + // Setting and reading the WM_COLORMAP_WINDOWS property + SetWMColormapWindows :: proc( + display: ^Display, + window: Window, + colormap_windows: [^]Window, + count: i32, + ) -> Status --- + GetWMColormapWindows :: proc( + display: ^Display, + window: Window, + colormap_windows: ^[^]Window, + count: ^i32, + ) -> Status --- + // Setting and reading the WM_ICON_SIZE_PROPERTY + AllocIconSize :: proc() -> ^XIconSize --- + SetIconSizes :: proc( + display: ^Display, + window: Window, + size_list: [^]XIconSize, + count: i32, + ) --- + GetIconSizes :: proc( + display: ^Display, + window: Window, + size_list: ^[^]XIconSize, + count: ^i32, + ) -> Status --- + // Using window manager convenience functions + mbSetWMProperties :: proc( + display: ^Display, + window: Window, + window_name: cstring, + icon_name: cstring, + argv: [^]cstring, + argc: i32, + normal_hints: ^XSizeHints, + wm_hints: ^XWMHints, + class_hints: ^XClassHint, + ) --- + SetWMProperties :: proc( + display: ^Display, + window: Window, + window_name: ^XTextProperty, + argv: [^]cstring, + argc: i32, + normal_hints: ^XSizeHints, + wm_hints: ^XWMHints, + class_hints: ^XWMHints, + ) --- + // Client to session manager communication + SetCommand :: proc( + display: ^Display, + window: Window, + argv: [^]cstring, + argc: i32, + ) --- + GetCommand :: proc( + display: ^Display, + window: Window, + argv: ^[^]cstring, + argc: ^i32, + ) -> Status --- + SetWMClientMachine :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) --- + GetWMClientMachine :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) -> Status --- + SetRGBColormaps :: proc( + display: ^Display, + window: Window, + colormap: ^XStandardColormap, + prop: Atom, + ) --- + GetRGBColormaps :: proc( + display: ^Display, + window: Window, + colormap: ^[^]XStandardColormap, + count: ^i32, + prop: Atom, + ) -> Status --- + // Keyboard utility functions + LookupKeysym :: proc( + event: ^XKeyEvent, + index: i32, + ) -> KeySym --- + KeycodeToKeysym :: proc( + display: ^Display, + keycode: KeyCode, + index: i32, + ) -> KeySym --- + KeysymToKeycode :: proc( + display: ^Display, + keysym: KeySym, + ) -> KeyCode --- + RefreshKeyboardMapping :: proc(event_map: ^XMappingEvent) --- + ConvertCase :: proc( + keysym: KeySym, + lower: ^KeySym, + upper: ^KeySym, + ) --- + StringToKeysym :: proc(str: cstring) -> KeySym --- + KeysymToString :: proc(keysym: KeySym) -> cstring --- + LookupString :: proc( + event: ^XKeyEvent, + buffer: [^]u8, + count: i32, + keysym: ^KeySym, + status: ^XComposeStatus, + ) -> i32 --- + RebindKeysym :: proc( + display: ^Display, + keysym: KeySym, + list: [^]KeySym, + mod_count: i32, + string: [^]u8, + num_bytes: i32, + ) --- + // Allocating permanent storage + Permalloc :: proc(size: u32) -> rawptr --- + // Parsing the window geometry + ParseGeometry :: proc( + parsestring: cstring, + x_ret: ^i32, + y_ret: ^i32, + width: ^u32, + height: ^u32, + ) -> i32 --- + WMGeometry :: proc( + display: ^Display, + screen_no: i32, + user_geom: cstring, + def_geom: cstring, + bwidth: u32, + hints: ^XSizeHints, + x_ret: ^i32, + y_ret: ^i32, + w_ret: ^u32, + h_ret: ^u32, + grav: ^Gravity, + ) -> i32 --- + // Creating, copying and destroying regions + CreateRegion :: proc() -> Region --- + PolygonRegion :: proc( + points: [^]XPoint, + n: i32, + fill: FillRule, + ) -> Region --- + SetRegion :: proc( + display: ^Display, + gc: GC, + region: Region, + ) --- + DestroyRegion :: proc(r: Region) --- + // Moving or shrinking regions + OffsetRegion :: proc(region: Region, dx, dy: i32) --- + ShrinkRegion :: proc(region: Region, dx, dy: i32) --- + // Computing with regions + ClipBox :: proc(region: Region, rect: ^XRectangle) --- + IntersectRegion :: proc(sra, srb, ret: Region) --- + UnionRegion :: proc(sra, srb, ret: Region) --- + UnionRectWithRegion :: proc(rect: ^XRectangle, src, dst: Region) --- + SubtractRegion :: proc(sra, srb, ret: Region) --- + XorRegion :: proc(sra, srb, ret: Region) --- + EmptyRegion :: proc(reg: Region) -> b32 --- + EqualRegion :: proc(a,b: Region) -> b32 --- + PointInRegion :: proc(reg: Region, x,y: i32) -> b32 --- + RectInRegion :: proc(reg: Region, x,y: i32, w,h: u32) -> b32 --- + // Using cut buffers + StoreBytes :: proc(display: ^Display, bytes: [^]u8, nbytes: i32) --- + StoreBuffer :: proc(display: ^Display, bytes: [^]u8, nbytes: i32, buffer: i32) --- + FetchBytes :: proc(display: ^Display, nbytes: ^i32) -> [^]u8 --- + FetchBuffer :: proc(display: ^Display, nbytes: ^i32, buffer: i32) -> [^]u8 --- + // Determining the appropriate visual types + GetVisualInfo :: proc( + display: ^Display, + mask: VisualInfoMask, + info: ^XVisualInfo, + nret: ^i32, + ) -> [^]XVisualInfo --- + MatchVisualInfo :: proc( + display: ^Display, + screen_no: i32, + depth: i32, + class: i32, + ret: ^XVisualInfo, + ) -> Status --- + // Manipulating images + CreateImage :: proc( + display: ^Display, + visual: ^Visual, + depth: u32, + format: ImageFormat, + offset: i32, + data: rawptr, + width: u32, + height: u32, + pad: i32, + stride: i32, + ) -> ^XImage --- + GetPixel :: proc( + image: ^XImage, + x: i32, + y: i32, + ) -> uint --- + PutPixel :: proc( + image: ^XImage, + x: i32, + y: i32, + pixel: uint, + ) --- + SubImage :: proc( + image: ^XImage, + x: i32, + y: i32, + w: u32, + h: u32, + ) -> ^XImage --- + AddPixel :: proc( + image: ^XImage, + value: int, + ) --- + StoreNamedColor :: proc( + display: ^Display, + colormap: Colormap, + name: cstring, + pixel: uint, + flags: ColorFlags, + ) --- + QueryColor :: proc( + display: ^Display, + colormap: Colormap, + color: ^XColor, + ) --- + QueryColors :: proc( + display: ^Display, + colormap: Colormap, + colors: [^]XColor, + ncolors: i32, + ) --- + QueryExtension :: proc( + display: ^Display, + name: cstring, + major_opcode_return: ^i32, + first_event_return: ^i32, + first_error_return: ^i32, + ) -> b32 --- + DestroyImage :: proc(image: ^XImage) --- + ResourceManagerString :: proc(display: ^Display) -> cstring --- + utf8SetWMProperties :: proc( + display: ^Display, + window: Window, + window_name: cstring, + icon_name: cstring, + argv: ^cstring, + argc: i32, + normal_hints: ^XSizeHints, + wm_hints: ^XWMHints, + class_hints: ^XClassHint, + ) --- + OpenIM :: proc( + display: ^Display, + rdb: XrmHashBucket, + res_name: cstring, + res_class: cstring, + ) -> XIM --- + SetLocaleModifiers :: proc(modifiers: cstring) -> cstring --- +} + +@(default_calling_convention="c") +foreign xlib { + XcmsLookupColor :: proc( + display: ^Display, + colormap: Colormap, + name: cstring, + exact: XcmsColor, + screen: XcmsColor, + format: XcmsColorFormat, + ) -> Status --- XcmsStoreColor :: proc( display: ^Display, colormap: Colormap, @@ -502,31 +1668,6 @@ foreign xlib { ncolors: XcmsColor, cflags: [^]b32, ) -> Status --- - XStoreNamedColor :: proc( - display: ^Display, - colormap: Colormap, - name: cstring, - pixel: uint, - flags: ColorFlags, - ) --- - XQueryColor :: proc( - display: ^Display, - colormap: Colormap, - color: ^XColor, - ) --- - XQueryColors :: proc( - display: ^Display, - colormap: Colormap, - colors: [^]XColor, - ncolors: i32, - ) --- - XQueryExtension :: proc( - display: ^Display, - name: cstring, - major_opcode_return: ^i32, - first_event_return: ^i32, - first_error_return: ^i32, - ) -> b32 --- XcmsQueryColor :: proc( display: ^Display, colormap: Colormap, @@ -553,12 +1694,9 @@ foreign xlib { // Color conversion context macros XcmsDisplayOfCCC :: proc(ccc: XcmsCCC) -> ^Display --- XcmsVisualOfCCC :: proc(ccc: XcmsCCC) -> ^Visual --- - XcmsScreenNumberOfCCC :: - proc(ccc: XcmsCCC) -> i32 --- - XcmsScreenWhitePointOfCCC :: - proc(ccc: XcmsCCC) -> XcmsColor --- - XcmsClientWhitePointOfCCC :: - proc(ccc: XcmsCCC) -> XcmsColor --- + XcmsScreenNumberOfCCC :: proc(ccc: XcmsCCC) -> i32 --- + XcmsScreenWhitePointOfCCC :: proc(ccc: XcmsCCC) -> XcmsColor --- + XcmsClientWhitePointOfCCC :: proc(ccc: XcmsCCC) -> XcmsColor --- // Modifying the attributes of color conversion context XcmsSetWhitePoint :: proc( ccc: XcmsCCC, @@ -790,1129 +1928,69 @@ foreign xlib { chroma: XcmsFloat, color: ^XcmsColor, ) -> Status --- - // Graphics context functions - XCreateGC :: proc( + XcmsAllocNamedColor :: proc( display: ^Display, - drawable: Drawable, - mask: GCAttributeMask, - attr: ^XGCValues, - ) -> GC --- - XCopyGC :: proc( - display: ^Display, - src: GC, - dst: GC, - mask: GCAttributeMask, - ) --- - XChangeGC :: proc( - display: ^Display, - gc: GC, - mask: GCAttributeMask, - values: ^XGCValues, - ) --- - XGetGCValues :: proc( - display: ^Display, - gc: GC, - mask: GCAttributeMask, - values: ^XGCValues, + colormap: Colormap, + name: cstring, + screen: ^XcmsColor, + exact: ^XcmsColor, + format: XcmsColorFormat, ) -> Status --- - XFreeGC :: proc(display: ^Display, gc: GC) --- - XGCContextFromGC :: proc(gc: GC) -> GContext --- - XFlushGC :: proc(display: ^Display, gc: GC) --- - // Convenience routines for GC - XSetState :: proc( - display: ^Display, - gc: GC, - fg: uint, - bg: uint, - fn: GCFunction, - pmask: uint, - ) --- - XSetForeground :: proc( - display: ^Display, - gc: GC, - fg: uint, - ) --- - XSetBackground :: proc( - display: ^Display, - gc: GC, - bg: uint, - ) --- - XSetFunction :: proc( - display: ^Display, - gc: GC, - fn: GCFunction, - ) --- - XSetPlaneMask :: proc( - display: ^Display, - gc: GC, - pmask: uint, - ) --- - XSetLineAttributes :: proc( - display: ^Display, - gc: GC, - width: u32, - line_style: LineStyle, - cap_style: CapStyle, - join_style: JoinStyle, - ) --- - XSetDashes :: proc( - display: ^Display, - gc: GC, - dash_offs: i32, - dash_list: [^]i8, - n: i32, - ) --- - XSetFillStyle :: proc( - display: ^Display, - gc: GC, - style: FillStyle, - ) --- - XSetFillRule :: proc( - display: ^Display, - gc: GC, - rule: FillRule, - ) --- - XQueryBestSize :: proc( - display: ^Display, - class: i32, - which: Drawable, - width: u32, - height: u32, - out_width: ^u32, - out_height: ^u32, + XcmsAllocColor :: proc( + display: ^Display, + colormap: Colormap, + color: ^XcmsColor, + format: XcmsColorFormat, ) -> Status --- - XQueryBestTile :: proc( - display: ^Display, - which: Drawable, - width: u32, - height: u32, - out_width: ^u32, - out_height: ^u32, - ) -> Status --- - XQueryBestStripple :: proc( - display: ^Display, - which: Drawable, - width: u32, - height: u32, - out_width: u32, - out_height: u32, - ) -> Status --- - XSetTile :: proc(display: ^Display, gc: GC, tile: Pixmap) --- - XSetStripple :: proc(display: ^Display, gc: GC, stripple: Pixmap) --- - XSetTSOrigin :: proc(display: ^Display, gc: GC, x: i32, y: i32) --- - XSetFont :: proc(display: ^Display, gc: GC, font: Font) --- - XSetClipOrigin :: proc(display: ^Display, gc: GC, x: i32, y: i32) --- - XSetClipMask :: proc(display: ^Display, gc: GC, pixmap: Pixmap) --- - XSetClipRectangles :: proc( - display: ^Display, - gc: GC, - x: i32, - y: i32, - rects: [^]XRectangle, - n: i32, - ordering: i32, - ) --- - XSetArcMode :: proc(display: ^Display, gc: GC, mode: ArcMode) --- - XSetSubwindowMode :: proc(display: ^Display, gc: GC, mode: SubwindowMode) --- - XSetGraphicsExposures :: proc(display: ^Display, gc: GC, exp: b32) --- - // Graphics functions - XClearArea :: proc( - display: ^Display, - window: Window, - x: i32, - y: i32, - width: u32, - height: u32, - exp: b32, - ) --- - XClearWindow :: proc( + XrmInitialize :: proc() --- + XrmGetStringDatabase :: proc(data: cstring) -> XrmDatabase --- + XrmGetResource :: proc(db: XrmDatabase, name: cstring, class: cstring, type_return: ^cstring, val_return: ^XrmValue) -> b32 --- + + /* ---- X11/XKBlib.h ---------------------------------------------------------*/ + + XkbQueryExtension :: proc( display: ^Display, - window: Window, - ) --- - XCopyArea :: proc( + opcode_return: ^i32, + event_base_return: ^i32, + error_base_return: ^i32, + major_return: ^i32, + minor_return: ^i32, + ) -> b32 --- + XkbUseExtension :: proc( display: ^Display, - src: Drawable, - dst: Drawable, - gc: GC, - src_x: i32, - src_y: i32, - width: u32, - height: u32, - dst_x: i32, - dst_y: i32, - ) --- - XCopyPlane :: proc( + major_return: ^i32, + minor_return: ^i32, + ) -> b32 --- + XkbGetMap :: proc( display: ^Display, - src: Drawable, - dst: Drawable, - gc: GC, - src_x: i32, - src_y: i32, - width: u32, - height: u32, - dst_x: i32, - dst_y: i32, - plane: uint, - ) --- - // Drawing lines, points, rectangles and arc - XDrawPoint :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - ) --- - XDrawPoints :: proc( - display: Display, - drawable: Drawable, - gc: GC, - point: [^]XPoint, - npoints: i32, - mode: CoordMode, - ) --- - XDrawLine :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x1: i32, - y1: i32, - x2: i32, - y2: i32, - ) --- - XDrawLines :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - points: [^]XPoint, - npoints: i32, - ) --- - XDrawSegments :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - segs: [^]XSegment, - nsegs: i32, - ) --- - XDrawRectangle :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - width: u32, - height: u32, - ) --- - XDrawRectangles :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - rects: [^]XRectangle, - nrects: i32, - ) --- - XDrawArc :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - width: u32, - height: u32, - angle1: i32, - angle2: i32, - ) --- - XDrawArcs :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - arcs: [^]XArc, - narcs: i32, - ) --- - // Filling areas - XFillRectangle :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - width: u32, - height: u32, - ) --- - XFillRectangles :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - rects: [^]XRectangle, - nrects: i32, - ) --- - XFillPolygon :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - points: [^]XPoint, - npoints: i32, - shape: Shape, - mode: CoordMode, - ) --- - XFillArc :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - width: u32, - height: u32, - angle1: i32, - angle2: i32, - ) --- - XFillArcs :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - arcs: [^]XArc, - narcs: i32, - ) --- - // Font metrics - XLoadFont :: proc(display: ^Display, name: cstring) -> Font --- - XQueryFont :: proc(display: ^Display, id: XID) -> ^XFontStruct --- - XLoadQueryFont :: proc(display: ^Display, name: cstring) -> ^XFontStruct --- - XFreeFont :: proc(display: ^Display, font_struct: ^XFontStruct) --- - XGetFontProperty :: proc(font_struct: ^XFontStruct, atom: Atom, ret: ^uint) -> b32 --- - XUnloadFont :: proc(display: ^Display, font: Font) --- - XListFonts :: proc(display: ^Display, pat: cstring, max: i32, count: ^i32) -> [^]cstring --- - XFreeFontNames :: proc(display: ^Display, list: [^]cstring) --- - XListFontsWithInfo :: proc( + which: XkbInfoMask, + device_spec: i32, + ) -> XkbDescPtr --- + XkbGetUpdatedMap :: proc( display: ^Display, - pat: cstring, - max: i32, - count: ^i32, - info: ^[^]XFontStruct, - ) -> [^]cstring --- - XFreeFontInfo :: proc(names: [^]cstring, info: [^]XFontStruct, count: i32) --- - // Computing character string sizes - XTextWidth :: proc(font_struct: ^XFontStruct, string: [^]u8, count: i32) -> i32 --- - XTextWidth16 :: proc(font_struct: ^XFontStruct, string: [^]XChar2b, count: i32) -> i32 --- - XTextExtents :: proc( - font_struct: ^XFontStruct, - string: [^]u8, - nchars: i32, - direction: ^FontDirection, - ascent: ^i32, - descent: ^i32, - ret: ^XCharStruct, - ) --- - XTextExtents16 :: proc( - font_struct: ^XFontStruct, - string: [^]XChar2b, - nchars: i32, - direction: ^FontDirection, - ascent: ^i32, - descent: ^i32, - ret: ^XCharStruct, - ) --- - XQueryTextExtents :: proc( - display: ^Display, - font_id: XID, - string: [^]u8, - nchars: i32, - direction: ^FontDirection, - ascent: ^i32, - descent: ^i32, - ret: ^XCharStruct, - ) --- - XQueryTextExtents16 :: proc( - display: ^Display, - font_id: XID, - string: [^]XChar2b, - nchars: i32, - direction: ^FontDirection, - ascent: ^i32, - descent: ^i32, - ret: ^XCharStruct, - ) --- - // Drawing complex text - XDrawText :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - items: XTextItem, - nitems: i32, - ) --- - XDrawText16 :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - items: XTextItem16, - nitems: i32, - ) --- - // Drawing text characters - XDrawString :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - string: [^]u8, - length: i32, - ) --- - XDrawString16 :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - string: [^]XChar2b, - length: i32, - ) --- - XDrawImageString :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - string: [^]u8, - length: i32, - ) --- - XDrawImageString16 :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - string: [^]XChar2b, - length: i32, - ) --- - // Transferring images between client and server - XInitImage :: proc(image: ^XImage) -> Status --- - XPutImage :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - image: ^XImage, - src_x: i32, - src_y: i32, - dst_x: i32, - dst_y: i32, - width: u32, - height: u32, - ) --- - XGetImage :: proc( - display: ^Display, - drawable: Drawable, - x: i32, - y: i32, - width: u32, - height: u32, - mask: uint, - format: ImageFormat, - ) -> ^XImage --- - XGetSubImage :: proc( - display: ^Display, - drawable: Drawable, - src_x: i32, - src_y: i32, - width: u32, - height: u32, - mask: uint, - format: ImageFormat, - dst: ^XImage, - dst_x: i32, - dst_y: i32, - ) -> ^XImage --- - // Window and session manager functions - XReparentWindow :: proc( + which: XkbInfoMask, + desc: XkbDescPtr, + ) -> b32 --- + XkbSelectEvents :: proc( display: ^Display, - window: Window, - parent: Window, - x: i32, - y: i32, - ) --- - XChangeSaveSet :: proc( + deviceID: u32, + bits_to_change: XkbEventMask, + values: XkbEventMask, + ) -> b32 --- + XkbSetDetectableAutoRepeat :: proc( display: ^Display, - window: Window, - mode: SaveSetChangeMode, - ) --- - XAddToSaveSet :: proc( + detectable: b32, + supported: ^b32, + ) -> b32 --- + XkbGetState :: proc ( display: ^Display, - window: Window, - ) --- - XRemoveFromSaveSet :: proc( + device_spec: u32, + return_state: XkbStatePtr, + ) -> Status --- + XkbGetKeySyms :: proc( display: ^Display, - window: Window, - ) --- - // Managing installed colormaps - XInstallColormap :: proc(display: ^Display, colormap: Colormap) --- - XUninstallColormap :: proc(display: ^Display, colormap: Colormap) --- - XListInstalledColormaps :: proc(display: ^Display, window: Window, n: ^i32) -> [^]Colormap --- - // Setting and retrieving font search paths - XSetFontPath :: proc(display: ^Display, dirs: [^]cstring, ndirs: i32) --- - XGetFontPath :: proc(display: ^Display, npaths: ^i32) -> [^]cstring --- - XFreeFontPath :: proc(list: [^]cstring) --- - // Grabbing the server - XGrabServer :: proc(display: ^Display) --- - XUngrabServer :: proc(display: ^Display) --- - // Killing clients - XKillClient :: proc(display: ^Display, resource: XID) --- - // Controlling the screen saver - XSetScreenSaver :: proc( - display: ^Display, - timeout: i32, - interval: i32, - blanking: ScreenSaverBlanking, - exposures: ScreenSavingExposures, - ) --- - XForceScreenSaver :: proc(display: ^Display, mode: ScreenSaverForceMode) --- - XActivateScreenSaver :: proc(display: ^Display) --- - XResetScreenSaver :: proc(display: ^Display) --- - XGetScreenSaver :: proc( - display: ^Display, - timeout: ^i32, - interval: ^i32, - blanking: ^ScreenSaverBlanking, - exposures: ^ScreenSavingExposures, - ) --- - // Controlling host address - XAddHost :: proc(display: ^Display, addr: ^XHostAddress) --- - XAddHosts :: proc(display: ^Display, hosts: [^]XHostAddress, nhosts: i32) --- - XListHosts :: proc(display: ^Display, nhosts: ^i32, state: [^]b32) -> [^]XHostAddress --- - XRemoveHost :: proc(display: ^Display, host: XHostAddress) --- - XRemoveHosts :: proc(display: ^Display, hosts: [^]XHostAddress, nhosts: i32) --- - // Access control list - XSetAccessControl :: proc(display: ^Display, mode: AccessControlMode) --- - XEnableAccessControl :: proc(display: ^Display) --- - XDisableAccessControl :: proc(display: ^Display) --- - // Events - XSelectInput :: proc(display: ^Display, window: Window, mask: EventMask) --- - XFlush :: proc(display: ^Display) --- - XSync :: proc(display: ^Display) --- - XEventsQueued :: proc(display: ^Display, mode: EventQueueMode) -> i32 --- - XPending :: proc(display: ^Display) -> i32 --- - XNextEvent :: proc(display: ^Display, event: ^XEvent) --- - XPeekEvent :: proc(display: ^Display, event: ^XEvent) --- - XGetEventData :: proc(display: ^Display, cookie: ^XGenericEventCookie) -> b32 --- - XFreeEventData :: proc(display: ^Display, cookie: ^XGenericEventCookie) --- - // Selecting events using a predicate procedure - XIfEvent :: proc( - display: ^Display, - event: ^XEvent, - predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, - ctx: rawptr, - ) --- - XCheckIfEvent :: proc( - display: ^Display, - event: ^XEvent, - predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, - arg: rawptr, - ) -> b32 --- - XPeekIfEvent :: proc( - display: ^Display, - event: ^XEvent, - predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, - ctx: rawptr, - ) --- - // Selecting events using a window or event mask - XWindowEvent :: proc( - display: ^Display, - window: Window, - mask: EventMask, - event: ^XEvent, - ) --- - XCheckWindowEvent :: proc( - display: ^Display, - window: Window, - mask: EventMask, - event: ^XEvent, - ) -> b32 --- - XMaskEvent :: proc( - display: ^Display, - mask: EventMask, - event: ^XEvent, - ) --- - XCheckMaskEvent :: proc( - display: ^Display, - mask: EventMask, - event: ^XEvent, - ) -> b32 --- - XCheckTypedEvent :: proc( - display: ^Display, - type: EventType, - event: ^XEvent, - ) -> b32 --- - XCheckTypedWindowEvent :: proc( - display: ^Display, - window: Window, - type: EventType, - event: ^XEvent, - ) -> b32 --- - // Putting events back - XPutBackEvent :: proc( - display: ^Display, - event: ^XEvent, - ) --- - // Sending events to other applications - XSendEvent :: proc( - display: ^Display, - window: Window, - propagate: b32, - mask: EventMask, - event: ^XEvent, - ) -> Status --- - // Getting the history of pointer motion - XDisplayMotionBufferSize :: proc(display: ^Display) -> uint --- - XGetMotionEvents :: proc( - display: ^Display, - window: Window, - start: Time, - stop: Time, - nevents: ^i32, - ) -> [^]XTimeCoord --- - // Enabling or disabling synchronization - XSetAfterFunction :: proc( - display: ^Display, - procedure: #type proc "c" (display: ^Display) -> i32, - ) -> i32 --- - XSynchronize :: proc( - display: ^Display, - onoff: b32, - ) -> i32 --- - // Error handling - XSetErrorHandler :: proc( - handler: #type proc "c" (display: ^Display, event: ^XErrorEvent) -> i32, - ) -> i32 --- - XGetErrorText :: proc( - display: ^Display, - code: i32, - buffer: [^]u8, - size: i32, - ) --- - XGetErrorDatabaseText :: proc( - display: ^Display, - name: cstring, - message: cstring, - default_string: cstring, - buffer: [^]u8, - size: i32, - ) --- - XDisplayName :: proc(string: cstring) -> cstring --- - XSetIOErrorHandler :: proc( - handler: #type proc "c" (display: ^Display) -> i32, - ) -> i32 --- - // Pointer grabbing - XGrabPointer :: proc( - display: ^Display, - grab_window: Window, - owner_events: b32, - mask: EventMask, - pointer_mode: GrabMode, - keyboard_mode: GrabMode, - confine_to: Window, - cursor: Cursor, - time: Time, - ) -> i32 --- - XUngrabPointer :: proc( - display: ^Display, - time: Time, - ) -> i32 --- - XChangeActivePointerGrab :: proc( - display: ^Display, - event_mask: EventMask, - cursor: Cursor, - time: Time, - ) --- - XGrabButton :: proc( - display: ^Display, - button: u32, - modifiers: InputMask, - grab_window: Window, - owner_events: b32, - event_mask: EventMask, - pointer_mode: GrabMode, - keyboard_mode: GrabMode, - confine_to: Window, - cursor: Cursor, - ) --- - XUngrabButton :: proc( - display: ^Display, - button: u32, - modifiers: InputMask, - grab_window: Window, - ) --- - XGrabKeyboard :: proc( - display: ^Display, - grab_window: Window, - owner_events: b32, - pointer_mode: GrabMode, - keyboard_mode: GrabMode, - time: Time, - ) -> i32 --- - XUngrabKeyboard :: proc( - display: ^Display, - time: Time, - ) --- - XGrabKey :: proc( - display: ^Display, - keycode: i32, - modifiers: InputMask, - grab_window: Window, - owner_events: b32, - pointer_mode: GrabMode, - keyboard_mode: GrabMode, - ) --- - XUngrabKey :: proc( - display: ^Display, - keycode: i32, - modifiers: InputMask, - grab_window: Window, - ) --- - // Resuming event processing - XAllowEvents :: proc(display: ^Display, evend_mode: AllowEventsMode, time: Time) --- - // Moving the pointer - XWarpPointer :: proc( - display: ^Display, - src_window: Window, - dst_window: Window, - src_x: i32, - src_y: i32, - src_width: u32, - src_height: u32, - dst_x: i32, - dst_y: i32, - ) --- - // Controlling input focus - XSetInputFocus :: proc( - display: ^Display, - focus: Window, - revert_to: FocusRevert, - time: Time, - ) --- - XGetInputFocus :: proc( - display: ^Display, - focus: ^Window, - revert_to: ^FocusRevert, - ) --- - // Manipulating the keyboard and pointer settings - XChangeKeyboardControl :: proc( - display: ^Display, - mask: KeyboardControlMask, - values: ^XKeyboardControl, - ) --- - XGetKeyboardControl :: proc( - display: ^Display, - values: ^XKeyboardState, - ) --- - XAutoRepeatOn :: proc(display: ^Display) --- - XAutoRepeatOff :: proc(display: ^Display) --- - XBell :: proc(display: ^Display, percent: i32) --- - XQueryKeymap :: proc(display: ^Display, keys: [^]u32) --- - XSetPointerMapping :: proc(display: ^Display, map_should_not_be_a_keyword: [^]u8, nmap: i32) -> i32 --- - XGetPointerMapping :: proc(display: ^Display, map_should_not_be_a_keyword: [^]u8, nmap: i32) -> i32 --- - XChangePointerControl :: proc( - display: ^Display, - do_accel: b32, - do_threshold: b32, - accel_numerator: i32, - accel_denominator: i32, - threshold: i32, - ) --- - XGetPointerControl :: proc( - display: ^Display, - accel_numerator: ^i32, - accel_denominator: ^i32, - threshold: ^i32, - ) --- - // Manipulating the keyboard encoding - XDisplayKeycodes :: proc( - display: ^Display, - min_keycodes: ^i32, - max_keycodes: ^i32, - ) --- - XGetKeyboardMapping :: proc( - display: ^Display, - first: KeyCode, - count: i32, - keysyms_per: ^i32, - ) -> ^KeySym --- - XChangeKeyboardMapping :: proc( - display: ^Display, - first: KeyCode, - keysyms_per: i32, - keysyms: [^]KeySym, - num_codes: i32, - ) --- - XNewModifiermap :: proc(max_keys_per_mode: i32) -> ^XModifierKeymap --- - XInsertModifiermapEntry :: proc( - modmap: ^XModifierKeymap, - keycode_entry: KeyCode, - modifier: i32, - ) -> ^XModifierKeymap --- - XDeleteModifiermapEntry :: proc( - modmap: ^XModifierKeymap, - keycode_entry: KeyCode, - modifier: i32, - ) -> ^XModifierKeymap --- - XFreeModifiermap :: proc(modmap: ^XModifierKeymap) --- - XSetModifierMapping :: proc(display: ^Display, modmap: ^XModifierKeymap) -> i32 --- - XGetModifierMapping :: proc(display: ^Display) -> ^XModifierKeymap --- - // Manipulating top-level windows - XIconifyWindow :: proc( - dipslay: ^Display, - window: Window, - screen_no: i32, - ) -> Status --- - XWithdrawWindow :: proc( - dipslay: ^Display, - window: Window, - screen_no: i32, - ) -> Status --- - XReconfigureWMWindow :: proc( - dipslay: ^Display, - window: Window, - screen_no: i32, - mask: WindowChangesMask, - changes: ^XWindowChanges, - ) -> Status --- - // Getting and setting the WM_NAME property - XSetWMName :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) --- - XGetWMName :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) -> Status --- - XStoreName :: proc( - display: ^Display, - window: Window, - name: cstring, - ) --- - XFetchName :: proc( - display: ^Display, - window: Window, - name: ^cstring, - ) -> Status --- - XSetWMIconName :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) --- - XGetWMIconName :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) -> Status --- - XSetIconName :: proc( - display: ^Display, - window: Window, - name: cstring, - ) --- - XGetIconName :: proc( - display: ^Display, - window: Window, - prop: ^cstring, - ) -> Status --- - // Setting and reading WM_HINTS property - XAllocWMHints :: proc() -> ^XWMHints --- - XSetWMHints :: proc( - display: ^Display, - window: Window, - hints: ^XWMHints, - ) --- - XGetWMHints :: proc( - display: ^Display, - window: Window, - ) -> ^XWMHints --- - // Setting and reading MW_NORMAL_HINTS property - XAllocSizeHints :: proc() -> ^XSizeHints --- - XSetWMNormalHints :: proc( - display: ^Display, - window: Window, - hints: ^XSizeHints, - ) --- - XGetWMNormalHints :: proc( - display: ^Display, - window: Window, - hints: ^XSizeHints, - flags: ^SizeHints, - ) -> Status --- - XSetWMSizeHints :: proc( - display: ^Display, - window: Window, - hints: ^XSizeHints, - prop: Atom, - ) --- - XGetWMSizeHints :: proc( - display: ^Display, - window: Window, - hints: ^XSizeHints, - masks: ^SizeHints, - prop: Atom, - ) -> Status --- - // Setting and reading the WM_CLASS property - XAllocClassHint :: proc() -> ^XClassHint --- - XSetClassHint :: proc( - display: ^Display, - window: Window, - hint: ^XClassHint, - ) --- - XGetClassHint :: proc( - display: ^Display, - window: Window, - hint: ^XClassHint, - ) -> Status --- - // Setting and reading WM_TRANSIENT_FOR property - XSetTransientForHint :: proc( - display: ^Display, - window: Window, - prop_window: Window, - ) --- - XGetTransientForHint :: proc( - display: ^Display, - window: Window, - prop_window: ^Window, - ) -> Status --- - // Setting and reading the WM_PROTOCOLS property - XSetWMProtocols :: proc( - display: ^Display, - window: Window, - protocols: [^]Atom, - count: i32, - ) -> Status --- - XGetWMProtocols :: proc( - display: ^Display, - window: Window, - protocols: ^[^]Atom, - count: ^i32, - ) -> Status --- - // Setting and reading the WM_COLORMAP_WINDOWS property - XSetWMColormapWindows :: proc( - display: ^Display, - window: Window, - colormap_windows: [^]Window, - count: i32, - ) -> Status --- - XGetWMColormapWindows :: proc( - display: ^Display, - window: Window, - colormap_windows: ^[^]Window, - count: ^i32, - ) -> Status --- - // Setting and reading the WM_ICON_SIZE_PROPERTY - XAllocIconSize :: proc() -> ^XIconSize --- - XSetIconSizes :: proc( - display: ^Display, - window: Window, - size_list: [^]XIconSize, - count: i32, - ) --- - XGetIconSizes :: proc( - display: ^Display, - window: Window, - size_list: ^[^]XIconSize, - count: ^i32, - ) -> Status --- - // Using window manager convenience functions - XmbSetWMProperties :: proc( - display: ^Display, - window: Window, - window_name: cstring, - icon_name: cstring, - argv: [^]cstring, - argc: i32, - normal_hints: ^XSizeHints, - wm_hints: ^XWMHints, - class_hints: ^XClassHint, - ) --- - XSetWMProperties :: proc( - display: ^Display, - window: Window, - window_name: ^XTextProperty, - argv: [^]cstring, - argc: i32, - normal_hints: ^XSizeHints, - wm_hints: ^XWMHints, - class_hints: ^XWMHints, - ) --- - // Client to session manager communication - XSetCommand :: proc( - display: ^Display, - window: Window, - argv: [^]cstring, - argc: i32, - ) --- - XGetCommand :: proc( - display: ^Display, - window: Window, - argv: ^[^]cstring, - argc: ^i32, - ) -> Status --- - XSetWMClientMachine :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) --- - XGetWMClientMachine :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) -> Status --- - XSetRGBColormaps :: proc( - display: ^Display, - window: Window, - colormap: ^XStandardColormap, - prop: Atom, - ) --- - XGetRGBColormaps :: proc( - display: ^Display, - window: Window, - colormap: ^[^]XStandardColormap, - count: ^i32, - prop: Atom, - ) -> Status --- - // Keyboard utility functions - XLookupKeysym :: proc( - event: ^XKeyEvent, - index: i32, - ) -> KeySym --- - XKeycodeToKeysym :: proc( - display: ^Display, - keycode: KeyCode, - index: i32, - ) -> KeySym --- - XKeysymToKeycode :: proc( - display: ^Display, - keysym: KeySym, - ) -> KeyCode --- - XRefreshKeyboardMapping :: proc(event_map: ^XMappingEvent) --- - XConvertCase :: proc( - keysym: KeySym, - lower: ^KeySym, - upper: ^KeySym, - ) --- - XStringToKeysym :: proc(str: cstring) -> KeySym --- - XKeysymToString :: proc(keysym: KeySym) -> cstring --- - XLookupString :: proc( - event: ^XKeyEvent, - buffer: [^]u8, - count: i32, - keysym: ^KeySym, - status: ^XComposeStatus, - ) -> i32 --- - XRebindKeysym :: proc( - display: ^Display, - keysym: KeySym, - list: [^]KeySym, - mod_count: i32, - string: [^]u8, - num_bytes: i32, - ) --- - // Allocating permanent storage - XPermalloc :: proc(size: u32) -> rawptr --- - // Parsing the window geometry - XParseGeometry :: proc( - parsestring: cstring, - x_ret: ^i32, - y_ret: ^i32, - width: ^u32, - height: ^u32, - ) -> i32 --- - XWMGeometry :: proc( - display: ^Display, - screen_no: i32, - user_geom: cstring, - def_geom: cstring, - bwidth: u32, - hints: ^XSizeHints, - x_ret: ^i32, - y_ret: ^i32, - w_ret: ^u32, - h_ret: ^u32, - grav: ^Gravity, - ) -> i32 --- - // Creating, copying and destroying regions - XCreateRegion :: proc() -> Region --- - XPolygonRegion :: proc( - points: [^]XPoint, - n: i32, - fill: FillRule, - ) -> Region --- - XSetRegion :: proc( - display: ^Display, - gc: GC, - region: Region, - ) --- - XDestroyRegion :: proc(r: Region) --- - // Moving or shrinking regions - XOffsetRegion :: proc(region: Region, dx, dy: i32) --- - XShrinkRegion :: proc(region: Region, dx, dy: i32) --- - // Computing with regions - XClipBox :: proc(region: Region, rect: ^XRectangle) --- - XIntersectRegion :: proc(sra, srb, ret: Region) --- - XUnionRegion :: proc(sra, srb, ret: Region) --- - XUnionRectWithRegion :: proc(rect: ^XRectangle, src, dst: Region) --- - XSubtractRegion :: proc(sra, srb, ret: Region) --- - XXorRegion :: proc(sra, srb, ret: Region) --- - XEmptyRegion :: proc(reg: Region) -> b32 --- - XEqualRegion :: proc(a,b: Region) -> b32 --- - XPointInRegion :: proc(reg: Region, x,y: i32) -> b32 --- - XRectInRegion :: proc(reg: Region, x,y: i32, w,h: u32) -> b32 --- - // Using cut buffers - XStoreBytes :: proc(display: ^Display, bytes: [^]u8, nbytes: i32) --- - XStoreBuffer :: proc(display: ^Display, bytes: [^]u8, nbytes: i32, buffer: i32) --- - XFetchBytes :: proc(display: ^Display, nbytes: ^i32) -> [^]u8 --- - XFetchBuffer :: proc(display: ^Display, nbytes: ^i32, buffer: i32) -> [^]u8 --- - // Determining the appropriate visual types - XGetVisualInfo :: proc( - display: ^Display, - mask: VisualInfoMask, - info: ^XVisualInfo, - nret: ^i32, - ) -> [^]XVisualInfo --- - XMatchVisualInfo :: proc( - display: ^Display, - screen_no: i32, - depth: i32, - class: i32, - ret: ^XVisualInfo, - ) -> Status --- - // Manipulating images - XCreateImage :: proc( - display: ^Display, - visual: ^Visual, - depth: u32, - format: ImageFormat, - offset: i32, - data: rawptr, - width: u32, - height: u32, - pad: i32, - stride: i32, - ) -> ^XImage --- - XGetPixel :: proc( - image: ^XImage, - x: i32, - y: i32, - ) -> uint --- - XPutPixel :: proc( - image: ^XImage, - x: i32, - y: i32, - pixel: uint, - ) --- - XSubImage :: proc( - image: ^XImage, - x: i32, - y: i32, - w: u32, - h: u32, - ) -> ^XImage --- - XAddPixel :: proc( - image: ^XImage, - value: int, - ) --- - XDestroyImage :: proc(image: ^XImage) --- + first: u32, + num: u32, + xkb: XkbDescPtr, + ) -> Status --- } diff --git a/vendor/x11/xlib/xlib_types.odin b/vendor/x11/xlib/xlib_types.odin index d333c3c79..d2bd2c5a3 100644 --- a/vendor/x11/xlib/xlib_types.odin +++ b/vendor/x11/xlib/xlib_types.odin @@ -24,6 +24,9 @@ Cursor :: XID Colormap :: XID GContext :: XID +RRCrtc :: XID +RROutput :: XID + KeyCode :: u8 /* ---- X11/Xlib.h ---------------------------------------------------------*/ @@ -1003,6 +1006,767 @@ XConnectionWatchProc :: #type proc "c" ( opening: b32, watch_data: rawptr) +/* ---- X11/extensions/XKBlib.h ---------------------------------------------------------*/ + +XkbAnyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: u32, +} + +XkbNewKeyboardNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + old_device: i32, + min_key_code: i32, + max_key_code: i32, + old_min_key_code: i32, + old_max_key_code: i32, + changed: u32, + req_major: i8, + req_minor: i8, +} + +XkbMapNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + changed: u32, + flags: u32, + first_type: i32, + num_types: i32, + min_key_code: KeyCode, + max_key_code: KeyCode, + first_key_sym: KeyCode, + first_key_act: KeyCode, + first_key_behavior: KeyCode, + first_key_explicit: KeyCode, + first_modmap_key: KeyCode, + first_vmodmap_key: KeyCode, + num_key_syms: i32, + num_key_acts: i32, + num_key_behaviors: i32, + num_key_explicit: i32, + num_modmap_keys: i32, + num_vmodmap_keys: i32, + vmods: u32, +} + +XkbStateNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + changed: u32, + group: i32, + base_group: i32, + latched_group: i32, + locked_group: i32, + mods: u32, + base_mods: u32, + latched_mods: u32, + locked_mods: u32, + compat_state: i32, + grab_mods: u8, + compat_grab_mods: u8, + lookup_mods: u8, + compat_lookup_mods: u8, + ptr_buttons: i32, + keycode: KeyCode, + event_type: i8, // should be EventType but needs to be i8 instead of i32 + req_major: i8, + req_minor: i8, +} + +XkbControlsNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + changed_ctrls: u32, + enabled_ctrls: u32, + enabled_ctrls_changes: u32, + num_groups: i32, + keycode: KeyCode, + event_type: i8, + req_major: i8, + req_minor: i8, +} + +XkbIndicatorNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + changed: u32, + state: u32, +} + +XkbNamesNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + changed: u32, + first_type: i32, + num_types: i32, + first_lvl: i32, + num_lvls: i32, + num_aliases: i32, + num_radio_groups: i32, + changed_vmods: u32, + changed_groups: u32, + changed_indicators: u32, + first_key: i32, + num_keys: i32, +} + +XkbCompatMapNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + changed_groups: u32, + first_si: i32, + num_si: i32, + num_total_si: i32, +} + +XkbBellNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + percent: i32, + pitch: i32, + duration: i32, + bell_class: i32, + bell_id: i32, + name: Atom, + window: Window, + event_only: b32, +} + +XkbActionMessageEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + keycode: KeyCode, + press: b32, + key_event_follows: b32, + group: i32, + mods: u32, + message: [XkbActionMessageLength+1]i8, +} + +XkbAccessXNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + detail: i32, + keycode: i32, + sk_delay: i32, + debounce_delay: i32, +} + +XkbExtensionDeviceNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + reason: u32, + supported: u32, + unsupported: u32, + first_btn: i32, + num_btns: i32, + leds_defined: u32, + led_state: u32, + led_class: i32, + led_id: i32, +} + +XkbEvent :: struct #raw_union { + type: XkbEventType, + any: XkbAnyEvent, + new_kbd: XkbNewKeyboardNotifyEvent, + _map: XkbMapNotifyEvent, + state: XkbStateNotifyEvent, + ctrls: XkbControlsNotifyEvent, + indicators: XkbIndicatorNotifyEvent, + names: XkbNamesNotifyEvent, + compat: XkbCompatMapNotifyEvent, + bell: XkbBellNotifyEvent, + message: XkbActionMessageEvent, + accessx: XkbAccessXNotifyEvent, + device: XkbExtensionDeviceNotifyEvent, + core: XEvent, +} + +/* ---- X11/extensions/XKBgeom.h ---------------------------------------------------------*/ + +XkbPointRec :: struct { + x: i16, + y: i16, +} +XkbPointPtr :: ^XkbPointRec + +XkbBoundsRec :: struct { + x1, x2: i16, + y1, y2: i16, +} +XkbBoundsPtr :: ^XkbBoundsRec + +XkbOutlineRec :: struct { + num_points: u16, + sz_points: u16, + corner_radius: u16, + points: [^]XkbPointRec, +} +XkbOutlinePtr :: ^XkbOutlineRec + +XkbShapeRec :: struct { + name: Atom, + num_outlines: u16, + sz_outlines: u16, + outlines: [^]XkbOutlineRec, + approx: XkbOutlinePtr, + primary: XkbOutlinePtr, + bounds: XkbBoundsRec, +} +XkbShapePtr :: ^XkbShapeRec + +XkbPropertyRec :: struct { + name: cstring, + value: cstring, +} +XkbPropertyPtr :: ^XkbPropertyRec + +XkbColorRec :: struct { + pixel: u32, + spec: ^u8, // cstring? +} +XkbColorPtr :: ^XkbColorRec + +XkbKeyRec :: struct { + name: XkbKeyNameRec, + gap: i16, + shape_ndx: u8, + color_ndx: u8, +} +XkbKeyPtr :: ^XkbKeyRec + +XkbRowRec :: struct { + top: i16, + left: i16, + num_keys: u16, + sz_keys: u16, + vertical: i32, + keys: [^]XkbKeyRec, + bounds: XkbBoundsRec, +} +XkbRowPtr :: ^XkbRowRec + +XkbAnyDoodadRec :: struct { + name: Atom, + type: u8, + priority: u8, + top: i16, + left: i16, + angle: i16, +} +XkbAnyDoodadPtr :: ^XkbAnyDoodadRec + +XkbShapeDoodadRec :: struct { + name: Atom, + type: u8, + priority: u8, + top: i16, + left: i16, + angle: i16, + color_ndx: u16, + shape_ndx: u16, +} +XkbShapeDoodadPtr :: ^XkbShapeDoodadRec + +XkbTextDoodadRec :: struct { + name: Atom, + type: u8, + priority: u8, + top: i16, + left: i16, + angle: i16, + color_ndx: u16, + text: cstring, + font: cstring, +} +XkbTextDoodadPtr :: ^XkbTextDoodadRec + +XkbIndicatorDoodadRec :: struct { + name: Atom, + type: u8, + priority: u8, + top: i16, + left: i16, + angle: i16, + color_ndx: u16, + on_color_ndx: u16, + off_color_ndx: u16, +} +XkbIndicatorDoodadPtr :: ^XkbIndicatorDoodadRec + +XkbLogoDoodadRec :: struct { + name: Atom, + type: u8, + priority: u8, + top: i16, + left: i16, + angle: i16, + color_ndx: u16, + shape_ndx: u16, + logo_name: cstring, +} +XkbLogoDoodadPtr :: ^XkbLogoDoodadRec + +XkbDoodadRec :: struct #raw_union { + any: XkbAnyDoodadRec, + shape: XkbShapeDoodadRec, + text: XkbTextDoodadRec, + indicator: XkbIndicatorDoodadRec, + logo: XkbLogoDoodadRec, +} +XkbDoodadPtr :: ^XkbDoodadRec + +XkbOverlayKeyRec :: struct { + over: XkbKeyNameRec, + under: XkbKeyNameRec, +} +XkbOverlayKeyPtr :: ^XkbOverlayKeyRec + +XkbOverlayRowRec :: struct { + row_under: u16, + num_keys: u16, + sz_keys: u16, + keys: [^]XkbOverlayKeyRec, +} +XkbOverlayRowPtr :: ^XkbOverlayRowRec + +XkbOverlayRec :: struct { + name: Atom, + section_under: XkbSectionPtr, + num_rows: u16, + sz_rows: u16, + rows: [^]XkbOverlayRowRec, + bounds: [^]XkbBoundsRec, +} +XkbOverlayPtr :: ^XkbOverlayRec + +XkbSectionRec :: struct { + name: Atom, + priority: u8, + top: i16, + left: i16, + width: u16, + height: u16, + angle: i16, + num_rows: u16, + num_doodads: u16, + num_overlays: u16, + sz_rows: u16, + sz_doodads: u16, + sz_overlays: u16, + rows: [^]XkbRowRec, + doodads: [^]XkbDoodadRec, + bounds: XkbBoundsRec, + overlays: [^]XkbOverlayRec, +} +XkbSectionPtr :: ^XkbSectionRec + +XkbGeometryRec :: struct { + name: Atom, + width_mm: u16, + height_mm: u16, + label_font: cstring, + label_color: XkbColorPtr, + base_color: XkbColorPtr, + sz_properties: u16, + sz_colors: u16, + sz_shapes: u16, + sz_sections: u16, + sz_doodads: u16, + sz_key_aliases: u16, + num_properties: u16, + num_colors: u16, + num_shapes: u16, + num_sections: u16, + num_doodads: u16, + num_key_aliases: u16, + properties: [^]XkbPropertyRec, + colors: [^]XkbColorRec, + shapes: [^]XkbShapeRec, + sections: [^]XkbSectionRec, + doodads: [^]XkbDoodadRec, + key_aliases: [^]XkbKeyAliasRec, +} +XkbGeometryPtr :: ^XkbGeometryRec + + +/* ---- X11/extensions/XKBstr.h ---------------------------------------------------------*/ + +XkbStateRec :: struct { + group: u8, + locked_group: u8, + base_group: u16, + latched_group: u16, + mods: u8, + base_mods: u8, + latched_mods: u8, + locked_mods: u8, + compat_state: u8, + grab_mods: u8, + compat_grab_mods: u8, + lookup_mods: u8, + compat_lookup_mods: u8, + ptr_buttons: u16, +} +XkbStatePtr :: ^XkbStateRec + +XkbModsRec :: struct { + mask: u8, /* effective mods */ + real_mods: u8, + vmods: u16, +} +XkbModsPtr :: ^XkbModsRec + +XkbKTMapEntryRec :: struct { + active: b32, + level: u8, + mods: XkbModsRec, +} +XkbKTMapEntryPtr :: ^XkbKTMapEntryRec + +XkbKeyTypeRec :: struct { + mod: XkbModsRec, + num_levels: u8, + map_count: u8, + _map: [^]XkbKTMapEntryRec, + preserve: [^]XkbModsRec, + name: Atom, + level_names: [^]Atom, +} +XkbKeyTypePtr :: ^XkbKeyTypeRec + +XkbBehavior :: struct { + type: u8, + data: u8, +} + +XkbAnyAction :: struct { + type: u8, + data: [XkbAnyActionDataSize]u8, +} + +XkbModAction :: struct { + type: u8, + flags: u8, + mask: u8, + real_mods: u8, + vmods1: u8, + vmods2: u8, +} + +XkbGroupAction :: struct { + type: u8, + flags: u8, + group_XXX: i8, +} + +XkbISOAction :: struct { + type: u8, + flags: u8, + mask: u8, + real_mods: u8, + group_XXX: i8, + affect: u8, + vmods1: u8, + vmods2: u8, +} + +XkbPtrAction :: struct { + type: u8, + flags: u8, + high_XXX: u8, + low_XXX: u8, + high_YYY: u8, + low_YYY: u8, +} + +XkbPtrBtnAction :: struct { + type: u8, + flags: u8, + count: u8, + button: u8, +} + +XkbPtrDfltAction :: struct { + type: u8, + flags: u8, + affect: u8, + value_XXX: u8, +} + +XkbSwitchScreenAction :: struct { + type: u8, + flags: u8, + screenXXX: i8, +} + +XkbCtrlsAction :: struct { + type: u8, + flags: u8, + ctrls3: u8, + ctrls2: u8, + ctrls1: u8, + ctrls0: u8, +} + +XkbMessageAction :: struct { + type: u8, + flags: u8, + message: [6]u8, +} + +XkbRedirectKeyAction :: struct { + type: u8, + new_key: u8, + mods_mask: u8, + mods: u8, + vmods_mask0: u8, + vmods_mask1: u8, + vmods0: u8, + vmods1: u8, +} + +XkbDeviceBtnAction :: struct { + type: u8, + flags: u8, + count: u8, + button: u8, + device: u8, +} + +XkbDeviceValuatorAction :: struct { + type: u8, + device: u8, + v1_what: u8, + v1_ndx: u8, + v1_value: u8, + v2_what: u8, + v2_ndx: u8, + v2_value: u8, +} + +XkbAction :: struct #raw_union { + any: XkbAnyAction, + mod: XkbModAction, + group: XkbGroupAction, + iso: XkbISOAction, + ptr: XkbPtrAction, + btn: XkbPtrBtnAction, + dflt: XkbPtrDfltAction, + screen: XkbSwitchScreenAction, + ctrls: XkbCtrlsAction, + msg: XkbMessageAction, + redirect: XkbRedirectKeyAction, + devbtn: XkbDeviceBtnAction, + devval: XkbDeviceValuatorAction, + type: u8, +} + +XkbControlsRec :: struct { + mk_dflt_btn: u8, + num_groups: u8, + groups_wrap: u8, + internal: XkbModsRec, + ignore_lock: XkbModsRec, + enabled_ctrls: u32, + repeat_delay: u16, + repeat_interval: u16, + slow_keys_delay: u16, + debounce_delay: u16, + mk_delay: u16, + mk_interval: u16, + mk_time_to_max: u16, + mk_max_speed: u16, + mk_curve: i16, + ax_options: u16, + ax_timeout: u16, + axt_opts_mask: u16, + axt_opts_values: u16, + axt_ctrls_mask: u32, + axt_ctrls_values: u32, + per_key_repeat: [XkbPerKeyBitArraySize]u8, +} +XkbControlsPtr :: ^XkbControlsRec + +XkbServerMapRec :: struct { + num_acts: u16, + size_acts: u16, + acts: [^]XkbAction, + + behaviors: [^]XkbBehavior, + key_acts: [^]u16, + explicit: [^]u8, + vmods: [XkbNumVirtualMods]u8, + vmodmap: [^]u16, +} +XkbServerMapPtr :: ^XkbServerMapRec + +XkbSymMapRec :: struct { + kt_index: [XkbNumKbdGroups]u8, + group_info: u8, + width: u8, + offset: u16, +} +XkbSymMapPtr :: ^XkbSymMapRec + +XkbClientMapRec :: struct { + size_types: u8, + num_types: u8, + types: [^]XkbKeyTypeRec, + + size_syms: u16, + num_syms: u16, + syms: [^]XID, // Keysym + key_sym_map: [^]XkbSymMapRec, + + modmap: [^]u8, +} +XkbClientMapPtr :: ^XkbClientMapRec + +XkbSymInterpretRec :: struct { + sym: XID, // KeySym + flags: u8, + match: u8, + mods: u8, + virtual_mod: u8, + act: XkbAnyAction, +} +XkbSymInterpretPtr :: ^XkbSymInterpretRec + +XkbCompatMapRec :: struct { + sym_interpret: [^]XkbSymInterpretRec, + groups: [XkbNumKbdGroups]XkbModsRec, + num_si: u16, + size_si: u16, +} +XkbCompatMapPtr :: ^XkbCompatMapRec + +XkbIndicatorMapRec :: struct { + flags: u8, + which_groups: u8, + groups: u8, + which_mods: u8, + mods: XkbModsRec, + ctrls: u32, +} +XkbIndicatorMapPtr :: ^XkbIndicatorMapRec + +XkbIndicatorRec :: struct { + phys_indicators: u64, + maps: [XkbNumIndicators]XkbIndicatorMapRec, +} +XkbIndicatorPtr :: ^XkbIndicatorRec + +XkbKeyNameRec :: struct { + name: [XkbKeyNameLength]i8, // Non nul-terminated string +} +XkbKeyNamePtr :: ^XkbKeyNameRec + +XkbKeyAliasRec :: struct { + real: [XkbKeyNameLength]i8, // Non nul-terminated string + alias: [XkbKeyNameLength]i8, // Non nul-terminated string +} +XkbKeyAliasPtr :: ^XkbKeyAliasRec + +XkbNamesRec :: struct { + keycodes: Atom, + geometry: Atom, + symbols: Atom, + types: Atom, + compat: Atom, + vmods: [XkbNumVirtualMods]Atom, + indicators: [XkbNumIndicators]Atom, + groups: [XkbNumKbdGroups]Atom, + keys: [^]XkbKeyNameRec, + key_aliases: [^]XkbKeyAliasRec, + radio_groups: [^]Atom, + phys_symbol: Atom, + num_keys: u8, + num_key_aliases: u8, + num_rg: u16, +} +XkbNamesPtr :: ^XkbNamesRec + +XkbDescRec :: struct { + display: ^Display, + flags: u16, + device_spec: u16, + min_key_code: KeyCode, + max_key_code: KeyCode, + + ctrls: XkbControlsPtr, + server: XkbServerMapPtr, + _map: XkbClientMapPtr, + indicators: XkbIndicatorPtr, + names: XkbNamesPtr, + compat: XkbCompatMapPtr, + geom: XkbGeometryPtr, +} +XkbDescPtr :: ^XkbDescRec + + /* ---- X11/Xcms.h ---------------------------------------------------------*/ XcmsColorFormat :: uint